#
tokens: 32626/50000 1/104 files (page 6/6)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 6 of 6. Use http://codebase.md/nspady/google-calendar-mcp?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .cursorignore
├── .dockerignore
├── .env.example
├── .github
│   └── workflows
│       ├── ci.yml
│       ├── publish.yml
│       └── README.md
├── .gitignore
├── .release-please-manifest.json
├── AGENTS.md
├── CHANGELOG.md
├── CLAUDE.md
├── docker-compose.yml
├── Dockerfile
├── docs
│   ├── advanced-usage.md
│   ├── architecture.md
│   ├── authentication.md
│   ├── deployment.md
│   ├── development.md
│   ├── docker.md
│   ├── README.md
│   └── testing.md
├── examples
│   ├── http-client.js
│   └── http-with-curl.sh
├── future_features
│   └── ARCHITECTURE_REDESIGN.md
├── gcp-oauth.keys.example.json
├── instructions
│   └── file_structure.md
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── scripts
│   ├── account-manager.js
│   ├── build.js
│   ├── dev.js
│   └── test-docker.sh
├── src
│   ├── auth
│   │   ├── client.ts
│   │   ├── paths.d.ts
│   │   ├── paths.js
│   │   ├── server.ts
│   │   ├── tokenManager.ts
│   │   └── utils.ts
│   ├── auth-server.ts
│   ├── config
│   │   └── TransportConfig.ts
│   ├── handlers
│   │   ├── core
│   │   │   ├── BaseToolHandler.ts
│   │   │   ├── BatchRequestHandler.ts
│   │   │   ├── CreateEventHandler.ts
│   │   │   ├── DeleteEventHandler.ts
│   │   │   ├── FreeBusyEventHandler.ts
│   │   │   ├── GetCurrentTimeHandler.ts
│   │   │   ├── GetEventHandler.ts
│   │   │   ├── ListCalendarsHandler.ts
│   │   │   ├── ListColorsHandler.ts
│   │   │   ├── ListEventsHandler.ts
│   │   │   ├── RecurringEventHelpers.ts
│   │   │   ├── SearchEventsHandler.ts
│   │   │   └── UpdateEventHandler.ts
│   │   ├── utils
│   │   │   └── datetime.ts
│   │   └── utils.ts
│   ├── index.ts
│   ├── schemas
│   │   └── types.ts
│   ├── server.ts
│   ├── services
│   │   └── conflict-detection
│   │       ├── config.ts
│   │       ├── ConflictAnalyzer.ts
│   │       ├── ConflictDetectionService.ts
│   │       ├── EventSimilarityChecker.ts
│   │       ├── index.ts
│   │       └── types.ts
│   ├── tests
│   │   ├── integration
│   │   │   ├── claude-mcp-integration.test.ts
│   │   │   ├── direct-integration.test.ts
│   │   │   ├── docker-integration.test.ts
│   │   │   ├── openai-mcp-integration.test.ts
│   │   │   └── test-data-factory.ts
│   │   └── unit
│   │       ├── console-statements.test.ts
│   │       ├── handlers
│   │       │   ├── BatchListEvents.test.ts
│   │       │   ├── BatchRequestHandler.test.ts
│   │       │   ├── CalendarNameResolution.test.ts
│   │       │   ├── create-event-blocking.test.ts
│   │       │   ├── CreateEventHandler.test.ts
│   │       │   ├── datetime-utils.test.ts
│   │       │   ├── duplicate-event-display.test.ts
│   │       │   ├── GetCurrentTimeHandler.test.ts
│   │       │   ├── GetEventHandler.test.ts
│   │       │   ├── list-events-registry.test.ts
│   │       │   ├── ListEventsHandler.test.ts
│   │       │   ├── RecurringEventHelpers.test.ts
│   │       │   ├── UpdateEventHandler.recurring.test.ts
│   │       │   ├── UpdateEventHandler.test.ts
│   │       │   ├── utils-conflict-format.test.ts
│   │       │   └── utils.test.ts
│   │       ├── index.test.ts
│   │       ├── schemas
│   │       │   ├── enhanced-properties.test.ts
│   │       │   ├── no-refs.test.ts
│   │       │   ├── schema-compatibility.test.ts
│   │       │   ├── tool-registration.test.ts
│   │       │   └── validators.test.ts
│   │       ├── services
│   │       │   └── conflict-detection
│   │       │       ├── ConflictAnalyzer.test.ts
│   │       │       └── EventSimilarityChecker.test.ts
│   │       └── utils
│   │           ├── event-id-validator.test.ts
│   │           └── field-mask-builder.test.ts
│   ├── tools
│   │   └── registry.ts
│   ├── transports
│   │   ├── http.ts
│   │   └── stdio.ts
│   ├── types
│   │   └── structured-responses.ts
│   └── utils
│       ├── event-id-validator.ts
│       ├── field-mask-builder.ts
│       └── response-builder.ts
├── tsconfig.json
├── tsconfig.lint.json
└── vitest.config.ts
```

# Files

--------------------------------------------------------------------------------
/src/tests/integration/direct-integration.test.ts:
--------------------------------------------------------------------------------

```typescript
   1 | import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach } from 'vitest';
   2 | import { Client } from "@modelcontextprotocol/sdk/client/index.js";
   3 | import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
   4 | import { spawn, ChildProcess } from 'child_process';
   5 | import { TestDataFactory, TestEvent } from './test-data-factory.js';
   6 | 
   7 | /**
   8 |  * Comprehensive Integration Tests for Google Calendar MCP
   9 |  * 
  10 |  * REQUIREMENTS TO RUN THESE TESTS:
  11 |  * 1. Valid Google OAuth credentials file at path specified by GOOGLE_OAUTH_CREDENTIALS env var
  12 |  * 2. Authenticated test account: Run `npm run dev auth:test` first
  13 |  * 3. TEST_CALENDAR_ID environment variable set to a real Google Calendar ID
  14 |  * 4. Network access to Google Calendar API
  15 |  * 
  16 |  * These tests exercise all MCP tools against a real test calendar and will:
  17 |  * - Create, modify, and delete real calendar events
  18 |  * - Make actual API calls to Google Calendar
  19 |  * - Require valid authentication tokens
  20 |  * 
  21 |  * Test Strategy:
  22 |  * 1. Create test events first
  23 |  * 2. Test read operations (list, search, freebusy)
  24 |  * 3. Test write operations (update)
  25 |  * 4. Clean up by deleting created events
  26 |  * 5. Track performance metrics throughout
  27 |  */
  28 | 
  29 | describe('Google Calendar MCP - Direct Integration Tests', () => {
  30 |   let client: Client;
  31 |   let serverProcess: ChildProcess;
  32 |   let testFactory: TestDataFactory;
  33 |   let createdEventIds: string[] = [];
  34 |   
  35 |   const TEST_CALENDAR_ID = process.env.TEST_CALENDAR_ID || 'primary';
  36 |   const SEND_UPDATES = 'none' as const;
  37 | 
  38 |   beforeAll(async () => {
  39 |     // Start the MCP server
  40 |     console.log('🚀 Starting Google Calendar MCP server...');
  41 |     
  42 |     // Filter out undefined values from process.env and set NODE_ENV=test
  43 |     const cleanEnv = Object.fromEntries(
  44 |       Object.entries(process.env).filter(([_, value]) => value !== undefined)
  45 |     ) as Record<string, string>;
  46 |     cleanEnv.NODE_ENV = 'test';
  47 |     
  48 |     serverProcess = spawn('node', ['build/index.js'], {
  49 |       stdio: ['pipe', 'pipe', 'pipe'],
  50 |       env: cleanEnv
  51 |     });
  52 | 
  53 |     // Wait for server to start
  54 |     await new Promise(resolve => setTimeout(resolve, 3000));
  55 | 
  56 |     // Create MCP client
  57 |     client = new Client({
  58 |       name: "integration-test-client",
  59 |       version: "1.0.0"
  60 |     }, {
  61 |       capabilities: {
  62 |         tools: {}
  63 |       }
  64 |     });
  65 | 
  66 |     // Connect to server
  67 |     const transport = new StdioClientTransport({
  68 |       command: 'node',
  69 |       args: ['build/index.js'],
  70 |       env: cleanEnv
  71 |     });
  72 |     
  73 |     await client.connect(transport);
  74 |     console.log('✅ Connected to MCP server');
  75 | 
  76 |     // Initialize test factory
  77 |     testFactory = new TestDataFactory();
  78 |   }, 30000);
  79 | 
  80 |   afterAll(async () => {
  81 |     console.log('\n🏁 Starting final cleanup...');
  82 |     
  83 |     // Final cleanup - ensure all test events are removed
  84 |     const allEventIds = testFactory.getCreatedEventIds();
  85 |     if (allEventIds.length > 0) {
  86 |       console.log(`📋 Found ${allEventIds.length} total events created during all tests`);
  87 |       await cleanupAllTestEvents();
  88 |     } else {
  89 |       console.log('✨ No additional events to clean up');
  90 |     }
  91 |     
  92 |     // Close client connection
  93 |     if (client) {
  94 |       await client.close();
  95 |       console.log('🔌 Closed MCP client connection');
  96 |     }
  97 |     
  98 |     // Terminate server process
  99 |     if (serverProcess && !serverProcess.killed) {
 100 |       serverProcess.kill();
 101 |       await new Promise(resolve => setTimeout(resolve, 1000));
 102 |       console.log('🛑 Terminated MCP server process');
 103 |     }
 104 | 
 105 |     // Log performance summary
 106 |     logPerformanceSummary();
 107 |     
 108 |     console.log('✅ Integration test cleanup completed successfully\n');
 109 |   }, 30000);
 110 | 
 111 |   beforeEach(() => {
 112 |     testFactory.clearPerformanceMetrics();
 113 |     createdEventIds = [];
 114 |   });
 115 | 
 116 |   afterEach(async () => {
 117 |     // Cleanup events created in this test
 118 |     if (createdEventIds.length > 0) {
 119 |       console.log(`🧹 Cleaning up ${createdEventIds.length} events from test...`);
 120 |       await cleanupTestEvents(createdEventIds);
 121 |       createdEventIds = [];
 122 |     }
 123 |   });
 124 | 
 125 |   describe('Tool Availability and Basic Functionality', () => {
 126 |     it('should list all expected tools', async () => {
 127 |       const startTime = testFactory.startTimer('list-tools');
 128 |       
 129 |       try {
 130 |         const tools = await client.listTools();
 131 |         
 132 |         testFactory.endTimer('list-tools', startTime, true);
 133 |         
 134 |         expect(tools.tools).toBeDefined();
 135 |         expect(tools.tools.length).toBe(10);
 136 |         
 137 |         const toolNames = tools.tools.map(t => t.name);
 138 |         expect(toolNames).toContain('get-current-time');
 139 |         expect(toolNames).toContain('list-calendars');
 140 |         expect(toolNames).toContain('list-events');
 141 |         expect(toolNames).toContain('search-events');
 142 |         expect(toolNames).toContain('list-colors');
 143 |         expect(toolNames).toContain('create-event');
 144 |         expect(toolNames).toContain('update-event');
 145 |         expect(toolNames).toContain('delete-event');
 146 |         expect(toolNames).toContain('get-freebusy');
 147 |         expect(toolNames).toContain('get-event');
 148 |       } catch (error) {
 149 |         testFactory.endTimer('list-tools', startTime, false, String(error));
 150 |         throw error;
 151 |       }
 152 |     });
 153 | 
 154 |     it('should list calendars including test calendar', async () => {
 155 |       const startTime = testFactory.startTimer('list-calendars');
 156 | 
 157 |       try {
 158 |         const result = await client.callTool({
 159 |           name: 'list-calendars',
 160 |           arguments: {}
 161 |         });
 162 | 
 163 |         testFactory.endTimer('list-calendars', startTime, true);
 164 | 
 165 |         expect(TestDataFactory.validateEventResponse(result)).toBe(true);
 166 |         const response = JSON.parse((result.content as any)[0].text);
 167 |         expect(response.calendars).toBeDefined();
 168 |         expect(Array.isArray(response.calendars)).toBe(true);
 169 |         expect(response.totalCount).toBeDefined();
 170 |         expect(typeof response.totalCount).toBe('number');
 171 |       } catch (error) {
 172 |         testFactory.endTimer('list-calendars', startTime, false, String(error));
 173 |         throw error;
 174 |       }
 175 |     });
 176 | 
 177 |     it('should list available colors', async () => {
 178 |       const startTime = testFactory.startTimer('list-colors');
 179 | 
 180 |       try {
 181 |         const result = await client.callTool({
 182 |           name: 'list-colors',
 183 |           arguments: {}
 184 |         });
 185 | 
 186 |         testFactory.endTimer('list-colors', startTime, true);
 187 | 
 188 |         expect(TestDataFactory.validateEventResponse(result)).toBe(true);
 189 |         const response = JSON.parse((result.content as any)[0].text);
 190 |         expect(response.event).toBeDefined();
 191 |         expect(response.calendar).toBeDefined();
 192 |       } catch (error) {
 193 |         testFactory.endTimer('list-colors', startTime, false, String(error));
 194 |         throw error;
 195 |       }
 196 |     });
 197 | 
 198 |     it('should get current time without timezone parameter (uses primary calendar timezone)', async () => {
 199 |       const startTime = testFactory.startTimer('get-current-time');
 200 |       
 201 |       try {
 202 |         const result = await client.callTool({
 203 |           name: 'get-current-time',
 204 |           arguments: {}
 205 |         });
 206 |         
 207 |         testFactory.endTimer('get-current-time', startTime, true);
 208 |         
 209 |         expect(TestDataFactory.validateEventResponse(result)).toBe(true);
 210 |         
 211 |         const response = JSON.parse((result.content as any)[0].text);
 212 |         expect(response.currentTime).toBeDefined();
 213 |         expect(response.currentTime).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/);
 214 |         expect(response.timezone).toBeTypeOf('string');
 215 |         expect(response.offset).toBeDefined();
 216 |         expect(response.isDST).toBeTypeOf('boolean');
 217 |       } catch (error) {
 218 |         testFactory.endTimer('get-current-time', startTime, false, String(error));
 219 |         throw error;
 220 |       }
 221 |     });
 222 | 
 223 |     it('should get current time with timezone parameter', async () => {
 224 |       const startTime = testFactory.startTimer('get-current-time-with-timezone');
 225 |       
 226 |       try {
 227 |         const result = await client.callTool({
 228 |           name: 'get-current-time',
 229 |           arguments: {
 230 |             timeZone: 'America/Los_Angeles'
 231 |           }
 232 |         });
 233 |         
 234 |         testFactory.endTimer('get-current-time-with-timezone', startTime, true);
 235 |         
 236 |         expect(TestDataFactory.validateEventResponse(result)).toBe(true);
 237 |         
 238 |         const response = JSON.parse((result.content as any)[0].text);
 239 |         expect(response.currentTime).toBeDefined();
 240 |         expect(response.currentTime).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/);
 241 |         expect(response.timezone).toBe('America/Los_Angeles');
 242 |         expect(response.offset).toBeDefined();
 243 |         expect(response.offset).toMatch(/^[+-]\d{2}:\d{2}$/);
 244 |         expect(response.isDST).toBeTypeOf('boolean');
 245 |       } catch (error) {
 246 |         testFactory.endTimer('get-current-time-with-timezone', startTime, false, String(error));
 247 |         throw error;
 248 |       }
 249 |     });
 250 | 
 251 |     it('should get event by ID', async () => {
 252 |       const startTime = testFactory.startTimer('get-event');
 253 |       
 254 |       try {
 255 |         // First create an event
 256 |         const eventData = TestDataFactory.createSingleEvent({
 257 |           summary: `Test Get Event By ID ${Date.now()}`
 258 |         });
 259 |         
 260 |         const eventId = await createTestEvent(eventData);
 261 |         createdEventIds.push(eventId);
 262 |         
 263 |         // Now get the event by ID
 264 |         const result = await client.callTool({
 265 |           name: 'get-event',
 266 |           arguments: {
 267 |             calendarId: TEST_CALENDAR_ID,
 268 |             eventId: eventId
 269 |           }
 270 |         });
 271 |         
 272 |         testFactory.endTimer('get-event', startTime, true);
 273 |         
 274 |         expect(TestDataFactory.validateEventResponse(result)).toBe(true);
 275 |         const response = JSON.parse((result.content as any)[0].text);
 276 |         expect(response.event).toBeDefined();
 277 |         expect(response.event.summary).toBe(eventData.summary);
 278 |         expect(response.event.id).toBe(eventId);
 279 |       } catch (error) {
 280 |         testFactory.endTimer('get-event', startTime, false, String(error));
 281 |         throw error;
 282 |       }
 283 |     });
 284 | 
 285 |     it('should return error for non-existent event ID', async () => {
 286 |       const startTime = testFactory.startTimer('get-event-not-found');
 287 |       
 288 |       const result = await client.callTool({
 289 |         name: 'get-event',
 290 |         arguments: {
 291 |           calendarId: TEST_CALENDAR_ID,
 292 |           eventId: 'non-existent-event-id-12345'
 293 |         }
 294 |       });
 295 |       
 296 |       // Errors are returned as text content
 297 |       const text = (result.content as any)[0]?.text;
 298 |       
 299 |       if (text && (text.includes('not found') || text.includes('Event with ID'))) {
 300 |         testFactory.endTimer('get-event-not-found', startTime, true);
 301 |         // This is expected - test passes
 302 |       } else {
 303 |         testFactory.endTimer('get-event-not-found', startTime, false, 'Expected error for non-existent event');
 304 |         throw new Error('Expected get-event to return error for non-existent event');
 305 |       }
 306 |     });
 307 | 
 308 |     it('should get event with specific fields', async () => {
 309 |       const startTime = testFactory.startTimer('get-event-with-fields');
 310 |       
 311 |       try {
 312 |         // First create an event with extended data
 313 |         const eventData = TestDataFactory.createColoredEvent('9', {
 314 |           summary: `Test Get Event With Fields ${Date.now()}`,
 315 |           description: 'Testing field filtering',
 316 |           location: 'Test Location'
 317 |         });
 318 |         
 319 |         const eventId = await createTestEvent(eventData);
 320 |         createdEventIds.push(eventId);
 321 |         
 322 |         // Get event with specific fields
 323 |         const result = await client.callTool({
 324 |           name: 'get-event',
 325 |           arguments: {
 326 |             calendarId: TEST_CALENDAR_ID,
 327 |             eventId: eventId,
 328 |             fields: ['colorId', 'description', 'location', 'created', 'updated']
 329 |           }
 330 |         });
 331 |         
 332 |         testFactory.endTimer('get-event-with-fields', startTime, true);
 333 |         
 334 |         expect(TestDataFactory.validateEventResponse(result)).toBe(true);
 335 |         const response = JSON.parse((result.content as any)[0].text);
 336 |         expect(response.event).toBeDefined();
 337 |         expect(response.event.summary).toBe(eventData.summary);
 338 |         expect(response.event.description).toBe(eventData.description);
 339 |         expect(response.event.location).toBe(eventData.location);
 340 |         // Color information may not be included when specific fields are requested
 341 |         // Just verify the event was retrieved with the requested fields
 342 |       } catch (error) {
 343 |         testFactory.endTimer('get-event-with-fields', startTime, false, String(error));
 344 |         throw error;
 345 |       }
 346 |     });
 347 |   });
 348 | 
 349 |   describe('Event Creation and Management Workflow', () => {
 350 |     describe('Single Event Operations', () => {
 351 |       it('should create, list, search, update, and delete a single event', async () => {
 352 |         // 1. Create event
 353 |         const eventData = TestDataFactory.createSingleEvent({
 354 |           summary: `Integration Test - Single Event Workflow ${Date.now()}`
 355 |         });
 356 |         
 357 |         const eventId = await createTestEvent(eventData);
 358 |         createdEventIds.push(eventId);
 359 |         
 360 |         // 2. List events to verify creation
 361 |         const timeRanges = TestDataFactory.getTimeRanges();
 362 |         await verifyEventInList(eventId, timeRanges.nextWeek);
 363 |         
 364 |         // 3. Search for the event
 365 |         await verifyEventInSearch(eventData.summary);
 366 |         
 367 |         // 4. Update the event
 368 |         await updateTestEvent(eventId, {
 369 |           summary: 'Updated Integration Test Event',
 370 |           location: 'Updated Location'
 371 |         });
 372 |         
 373 |         // 5. Verify update took effect
 374 |         await verifyEventInSearch('Integration');
 375 |         
 376 |         // 6. Delete will happen in afterEach cleanup
 377 |       });
 378 | 
 379 |       it('should handle all-day events', async () => {
 380 |         const allDayEvent = TestDataFactory.createAllDayEvent({
 381 |           summary: `Integration Test - All Day Event ${Date.now()}`
 382 |         });
 383 |         
 384 |         const eventId = await createTestEvent(allDayEvent);
 385 |         createdEventIds.push(eventId);
 386 |         
 387 |         // Verify all-day event appears in searches
 388 |         await verifyEventInSearch(allDayEvent.summary);
 389 |       });
 390 | 
 391 |       it('should correctly display all-day events in non-UTC timezones', async () => {
 392 |         // Create an all-day event for a specific date
 393 |         // For all-day events, use date-only format (YYYY-MM-DD)
 394 |         const startDate = '2025-03-15'; // March 15, 2025
 395 |         const endDate = '2025-03-16';   // March 16, 2025 (exclusive)
 396 |         
 397 |         // Create all-day event
 398 |         const createResult = await client.callTool({
 399 |           name: 'create-event',
 400 |           arguments: {
 401 |             calendarId: TEST_CALENDAR_ID,
 402 |             summary: `All-Day Event Timezone Test ${Date.now()}`,
 403 |             description: 'Testing all-day event display in different timezones',
 404 |             start: startDate,
 405 |             end: endDate
 406 |           }
 407 |         });
 408 |         
 409 |         const eventId = extractEventId(createResult);
 410 |         expect(eventId).toBeTruthy();
 411 |         if (eventId) createdEventIds.push(eventId);
 412 |         
 413 |         // Test 1: List events without timezone (should use calendar's default)
 414 |         const listDefaultTz = await client.callTool({
 415 |           name: 'list-events',
 416 |           arguments: {
 417 |             calendarId: TEST_CALENDAR_ID,
 418 |             timeMin: '2025-03-14T00:00:00',
 419 |             timeMax: '2025-03-17T23:59:59'
 420 |           }
 421 |         });
 422 |         
 423 |         const defaultText = (listDefaultTz.content as any)[0].text;
 424 |         console.log('Default timezone listing:', defaultText);
 425 |         
 426 |         // Test 2: List events with UTC timezone
 427 |         const listUTC = await client.callTool({
 428 |           name: 'list-events',
 429 |           arguments: {
 430 |             calendarId: TEST_CALENDAR_ID,
 431 |             timeMin: '2025-03-14T00:00:00Z',
 432 |             timeMax: '2025-03-17T23:59:59Z',
 433 |             timeZone: 'UTC'
 434 |           }
 435 |         });
 436 |         
 437 |         const utcText = (listUTC.content as any)[0].text;
 438 |         console.log('UTC listing:', utcText);
 439 |         
 440 |         // Test 3: List events with Pacific timezone (UTC-7/8)
 441 |         const listPacific = await client.callTool({
 442 |           name: 'list-events',
 443 |           arguments: {
 444 |             calendarId: TEST_CALENDAR_ID,
 445 |             timeMin: '2025-03-14T00:00:00-07:00',
 446 |             timeMax: '2025-03-17T23:59:59-07:00',
 447 |             timeZone: 'America/Los_Angeles'
 448 |           }
 449 |         });
 450 |         
 451 |         const pacificResponse = JSON.parse((listPacific.content as any)[0].text);
 452 |         console.log('Pacific timezone listing:', JSON.stringify(pacificResponse, null, 2));
 453 |         
 454 |         // Parse the other responses too
 455 |         const defaultResponse = JSON.parse(defaultText);
 456 |         const utcResponse = JSON.parse(utcText);
 457 |         
 458 |         // All listings should have events with dates on March 15, 2025
 459 |         // Check that all responses have events
 460 |         expect(defaultResponse.events).toBeDefined();
 461 |         expect(utcResponse.events).toBeDefined();
 462 |         expect(pacificResponse.events).toBeDefined();
 463 |         
 464 |         // For all-day events, the date should be 2025-03-15
 465 |         if (defaultResponse.events.length > 0) {
 466 |           const event = defaultResponse.events[0];
 467 |           if (event.start.date) {
 468 |             expect(event.start.date).toBe('2025-03-15');
 469 |           }
 470 |         }
 471 |       });
 472 | 
 473 |       it('should handle events with attendees', async () => {
 474 |         const eventWithAttendees = TestDataFactory.createEventWithAttendees({
 475 |           summary: `Integration Test - Event with Attendees ${Date.now()}`
 476 |         });
 477 |         
 478 |         const eventId = await createTestEvent(eventWithAttendees);
 479 |         createdEventIds.push(eventId);
 480 |         
 481 |         await verifyEventInSearch(eventWithAttendees.summary);
 482 |       });
 483 | 
 484 |       it('should handle colored events', async () => {
 485 |         const coloredEvent = TestDataFactory.createColoredEvent('9', {
 486 |           summary: `Integration Test - Colored Event ${Date.now()}`
 487 |         });
 488 |         
 489 |         const eventId = await createTestEvent(coloredEvent);
 490 |         createdEventIds.push(eventId);
 491 |         
 492 |         await verifyEventInSearch(coloredEvent.summary);
 493 |       });
 494 | 
 495 |       it('should create event without timezone and use calendar default', async () => {
 496 |         // First, get the calendar details to know the expected default timezone
 497 |         const calendarResult = await client.callTool({
 498 |           name: 'list-calendars',
 499 |           arguments: {}
 500 |         });
 501 |         
 502 |         expect(TestDataFactory.validateEventResponse(calendarResult)).toBe(true);
 503 |         
 504 |         // Create event data without timezone
 505 |         const eventData = TestDataFactory.createSingleEvent({
 506 |           summary: `Integration Test - Default Timezone Event ${Date.now()}`
 507 |         });
 508 |         
 509 |         // Remove timezone from the event data to test default behavior
 510 |         const eventDataWithoutTimezone = {
 511 |           ...eventData,
 512 |           timeZone: undefined
 513 |         };
 514 |         delete eventDataWithoutTimezone.timeZone;
 515 |         
 516 |         // Also convert datetime strings to timezone-naive format
 517 |         eventDataWithoutTimezone.start = eventDataWithoutTimezone.start.replace(/[+-]\d{2}:\d{2}$|Z$/, '');
 518 |         eventDataWithoutTimezone.end = eventDataWithoutTimezone.end.replace(/[+-]\d{2}:\d{2}$|Z$/, '');
 519 |         
 520 |         const startTime = testFactory.startTimer('create-event-default-timezone');
 521 |         
 522 |         try {
 523 |           const result = await client.callTool({
 524 |             name: 'create-event',
 525 |             arguments: {
 526 |               calendarId: TEST_CALENDAR_ID,
 527 |               ...eventDataWithoutTimezone
 528 |             }
 529 |           });
 530 |           
 531 |           testFactory.endTimer('create-event-default-timezone', startTime, true);
 532 |           
 533 |           expect(TestDataFactory.validateEventResponse(result)).toBe(true);
 534 |           
 535 |           const eventId = extractEventId(result);
 536 |           expect(eventId).toBeTruthy();
 537 |           
 538 |           createdEventIds.push(eventId!);
 539 |           testFactory.addCreatedEventId(eventId!);
 540 |           
 541 |           // Verify the event was created successfully and shows up in searches
 542 |           await verifyEventInSearch(eventData.summary);
 543 |           
 544 |           // Verify the response contains expected event data
 545 |           const response = JSON.parse((result.content as any)[0].text);
 546 |           expect(response.event).toBeDefined();
 547 |           expect(response.event.summary).toBe(eventData.summary);
 548 |           
 549 |           console.log('✅ Event created successfully without explicit timezone - using calendar default');
 550 |         } catch (error) {
 551 |           testFactory.endTimer('create-event-default-timezone', startTime, false, String(error));
 552 |           throw error;
 553 |         }
 554 |       });
 555 |     });
 556 | 
 557 |     describe('Recurring Event Operations', () => {
 558 |       it('should create and manage recurring events', async () => {
 559 |         // Create recurring event with unique name
 560 |         const timestamp = Date.now();
 561 |         const recurringEvent = TestDataFactory.createRecurringEvent({
 562 |           summary: `Integration Test - Recurring Weekly Meeting ${timestamp}`
 563 |         });
 564 |         
 565 |         const eventId = await createTestEvent(recurringEvent);
 566 |         createdEventIds.push(eventId);
 567 |         
 568 |         // Verify recurring event
 569 |         await verifyEventInSearch(recurringEvent.summary);
 570 |         
 571 |         // Test different update scopes
 572 |         await testRecurringEventUpdates(eventId);
 573 |       });
 574 | 
 575 | 
 576 |       it('should handle update-event with future instances scope (thisAndFollowing)', async () => {
 577 |         // Create a recurring event with unique name
 578 |         const timestamp = Date.now();
 579 |         const recurringEvent = TestDataFactory.createRecurringEvent({
 580 |           summary: `Weekly Team Meeting - Future Instances Test ${timestamp}`,
 581 |           description: 'This is a recurring weekly meeting',
 582 |           location: 'Conference Room A'
 583 |         });
 584 |         
 585 |         const eventId = await createTestEvent(recurringEvent);
 586 |         createdEventIds.push(eventId);
 587 |         
 588 |         // Wait for event to be searchable
 589 |         await new Promise(resolve => setTimeout(resolve, 2000));
 590 |         
 591 |         // Calculate a future date (3 weeks from now)
 592 |         const futureDate = new Date();
 593 |         futureDate.setDate(futureDate.getDate() + 21);
 594 |         const futureStartDate = TestDataFactory.formatDateTimeRFC3339WithTimezone(futureDate);
 595 |         
 596 |         // Update future instances
 597 |         const updateResult = await client.callTool({
 598 |           name: 'update-event',
 599 |           arguments: {
 600 |             calendarId: TEST_CALENDAR_ID,
 601 |             eventId: eventId,
 602 |             modificationScope: 'thisAndFollowing',
 603 |             futureStartDate: futureStartDate,
 604 |             summary: 'Updated Team Meeting - Future Instances',
 605 |             location: 'New Conference Room',
 606 |             timeZone: 'America/Los_Angeles',
 607 |             sendUpdates: SEND_UPDATES
 608 |           }
 609 |         });
 610 |         
 611 |         expect(TestDataFactory.validateEventResponse(updateResult)).toBe(true);
 612 |         const responseText = (updateResult.content as any)[0].text;
 613 |         const response = JSON.parse(responseText);
 614 |         expect(response.event).toBeDefined();
 615 |         expect(response.event.summary).toBe('Updated Team Meeting - Future Instances');
 616 |       });
 617 | 
 618 |       it('should maintain backward compatibility with existing update-event calls', async () => {
 619 |         // Create a recurring event with unique name
 620 |         const timestamp = Date.now();
 621 |         const recurringEvent = TestDataFactory.createRecurringEvent({
 622 |           summary: `Weekly Team Meeting - Backward Compatibility Test ${timestamp}`
 623 |         });
 624 |         
 625 |         const eventId = await createTestEvent(recurringEvent);
 626 |         createdEventIds.push(eventId);
 627 |         
 628 |         // Wait for event to be searchable
 629 |         await new Promise(resolve => setTimeout(resolve, 2000));
 630 |         
 631 |         // Legacy call format without new parameters (should default to 'all' scope)
 632 |         const updateResult = await client.callTool({
 633 |           name: 'update-event',
 634 |           arguments: {
 635 |             calendarId: TEST_CALENDAR_ID,
 636 |             eventId: eventId,
 637 |             summary: 'Updated Weekly Meeting - All Instances',
 638 |             location: 'Conference Room B',
 639 |             timeZone: 'America/Los_Angeles',
 640 |             sendUpdates: SEND_UPDATES
 641 |             // No modificationScope, originalStartTime, or futureStartDate
 642 |           }
 643 |         });
 644 |         
 645 |         expect(TestDataFactory.validateEventResponse(updateResult)).toBe(true);
 646 |         const responseText = (updateResult.content as any)[0].text;
 647 |         const response = JSON.parse(responseText);
 648 |         expect(response.event).toBeDefined();
 649 |         expect(response.event.summary).toBe('Updated Weekly Meeting - All Instances');
 650 |         
 651 |         // Verify all instances were updated
 652 |         await verifyEventInSearch('Updated Weekly Meeting - All Instances');
 653 |       });
 654 | 
 655 |       it('should handle validation errors for missing required fields', async () => {
 656 |         // Test case 1: Missing originalStartTime for 'thisEventOnly' scope
 657 |         const invalidSingleResult = await client.callTool({
 658 |           name: 'update-event',
 659 |           arguments: {
 660 |             calendarId: TEST_CALENDAR_ID,
 661 |             eventId: 'recurring123',
 662 |             modificationScope: 'thisEventOnly',
 663 |             timeZone: 'America/Los_Angeles',
 664 |             summary: 'Test Update'
 665 |             // missing originalStartTime
 666 |           }
 667 |         });
 668 |         
 669 |         // Errors are returned as text content
 670 |         const invalidSingleText = (invalidSingleResult.content as any)[0]?.text;
 671 |         expect(invalidSingleText).toContain('originalStartTime');
 672 |         
 673 |         // Test case 2: Missing futureStartDate for 'thisAndFollowing' scope
 674 |         const invalidFutureResult = await client.callTool({
 675 |           name: 'update-event',
 676 |           arguments: {
 677 |             calendarId: TEST_CALENDAR_ID,
 678 |             eventId: 'recurring123',
 679 |             modificationScope: 'thisAndFollowing',
 680 |             timeZone: 'America/Los_Angeles',
 681 |             summary: 'Test Update'
 682 |             // missing futureStartDate
 683 |           }
 684 |         });
 685 |         
 686 |         // Errors are returned as text content
 687 |         const invalidFutureText = (invalidFutureResult.content as any)[0]?.text;
 688 |         expect(invalidFutureText).toContain('futureStartDate');
 689 |       });
 690 | 
 691 |       it('should reject non-"all" scopes for single (non-recurring) events', async () => {
 692 |         // Create a single (non-recurring) event
 693 |         const singleEvent = TestDataFactory.createSingleEvent({
 694 |           summary: `Single Event - Scope Test ${Date.now()}`
 695 |         });
 696 |         
 697 |         const eventId = await createTestEvent(singleEvent);
 698 |         createdEventIds.push(eventId);
 699 |         
 700 |         // Wait for event to be created
 701 |         await new Promise(resolve => setTimeout(resolve, 1000));
 702 |         
 703 |         // Try to update with 'thisEventOnly' scope (should fail)
 704 |         const invalidResult = await client.callTool({
 705 |           name: 'update-event',
 706 |           arguments: {
 707 |             calendarId: TEST_CALENDAR_ID,
 708 |             eventId: eventId,
 709 |             modificationScope: 'thisEventOnly',
 710 |             originalStartTime: singleEvent.start,
 711 |             summary: 'Updated Single Event',
 712 |             timeZone: 'America/Los_Angeles',
 713 |             sendUpdates: SEND_UPDATES
 714 |           }
 715 |         });
 716 |         
 717 |         // Errors are returned as text content
 718 |         const errorText = (invalidResult.content as any)[0]?.text?.toLowerCase() || '';
 719 |         expect(errorText).toMatch(/scope.*only applies to recurring events|not a recurring event/i);
 720 |       });
 721 | 
 722 |       it('should handle complex recurring event updates with all fields', async () => {
 723 |         // Create a complex recurring event
 724 |         const complexEvent = TestDataFactory.createRecurringEvent({
 725 |           summary: `Complex Weekly Meeting ${Date.now()}`,
 726 |           description: 'Original meeting with all fields',
 727 |           location: 'Executive Conference Room',
 728 |           colorId: '9'
 729 |         });
 730 |         
 731 |         // Add attendees and reminders
 732 |         const complexEventWithExtras = {
 733 |           ...complexEvent,
 734 |           attendees: [
 735 |             { email: '[email protected]' },
 736 |             { email: '[email protected]' }
 737 |           ],
 738 |           reminders: {
 739 |             useDefault: false,
 740 |             overrides: [
 741 |               { method: 'email' as const, minutes: 1440 }, // 1 day before
 742 |               { method: 'popup' as const, minutes: 15 }
 743 |             ]
 744 |           }
 745 |         };
 746 |         
 747 |         const eventId = await createTestEvent(complexEventWithExtras);
 748 |         createdEventIds.push(eventId);
 749 |         
 750 |         // Wait for event to be searchable
 751 |         await new Promise(resolve => setTimeout(resolve, 2000));
 752 |         
 753 |         // Update with all fields
 754 |         const updateResult = await client.callTool({
 755 |           name: 'update-event',
 756 |           arguments: {
 757 |             calendarId: TEST_CALENDAR_ID,
 758 |             eventId: eventId,
 759 |             modificationScope: 'all',
 760 |             summary: 'Updated Complex Meeting - All Fields',
 761 |             description: 'Updated meeting with all the bells and whistles',
 762 |             location: 'New Executive Conference Room',
 763 |             colorId: '11', // Different color
 764 |             attendees: [
 765 |               { email: '[email protected]' },
 766 |               { email: '[email protected]' },
 767 |               { email: '[email protected]' } // Added attendee
 768 |             ],
 769 |             reminders: {
 770 |               useDefault: false,
 771 |               overrides: [
 772 |                 { method: 'email' as const, minutes: 1440 },
 773 |                 { method: 'popup' as const, minutes: 30 } // Changed from 15 to 30
 774 |               ]
 775 |             },
 776 |             timeZone: 'America/Los_Angeles',
 777 |             sendUpdates: SEND_UPDATES
 778 |           }
 779 |         });
 780 |         
 781 |         expect(TestDataFactory.validateEventResponse(updateResult)).toBe(true);
 782 |         const updateResponse = JSON.parse((updateResult.content as any)[0].text);
 783 |         expect(updateResponse.event).toBeDefined();
 784 |         expect(updateResponse.event.summary).toBe('Updated Complex Meeting - All Fields');
 785 |         
 786 |         // Verify the update
 787 |         await verifyEventInSearch('Updated Complex Meeting - All Fields');
 788 |       });
 789 | 
 790 |       it('should convert timed event to all-day event and back (Issue #118)', async () => {
 791 |         console.log('\n🧪 Testing timed ↔ all-day event conversion (Issue #118)...');
 792 | 
 793 |         // Step 1: Create a timed event
 794 |         const timedEvent = TestDataFactory.createSingleEvent({
 795 |           summary: `Conversion Test ${Date.now()}`,
 796 |           description: 'Testing conversion between timed and all-day formats'
 797 |         });
 798 | 
 799 |         const eventId = await createTestEvent(timedEvent);
 800 |         createdEventIds.push(eventId);
 801 |         console.log(`✅ Created timed event: ${eventId}`);
 802 | 
 803 |         // Wait for event to be created
 804 |         await new Promise(resolve => setTimeout(resolve, 2000));
 805 | 
 806 |         // Step 2: Convert timed event to all-day event
 807 |         console.log('🔄 Converting timed event to all-day...');
 808 |         const toAllDayResult = await client.callTool({
 809 |           name: 'update-event',
 810 |           arguments: {
 811 |             calendarId: TEST_CALENDAR_ID,
 812 |             eventId: eventId,
 813 |             start: '2025-10-25',
 814 |             end: '2025-10-26',
 815 |             sendUpdates: SEND_UPDATES
 816 |           }
 817 |         });
 818 | 
 819 |         expect(TestDataFactory.validateEventResponse(toAllDayResult)).toBe(true);
 820 |         const allDayResponse = JSON.parse((toAllDayResult.content as any)[0].text);
 821 |         expect(allDayResponse.event).toBeDefined();
 822 |         expect(allDayResponse.event.start.date).toBe('2025-10-25');
 823 |         expect(allDayResponse.event.end.date).toBe('2025-10-26');
 824 |         expect(allDayResponse.event.start.dateTime).toBeUndefined();
 825 |         console.log('✅ Successfully converted to all-day event');
 826 | 
 827 |         // Wait for update to propagate
 828 |         await new Promise(resolve => setTimeout(resolve, 2000));
 829 | 
 830 |         // Step 3: Convert all-day event back to timed event
 831 |         console.log('🔄 Converting all-day event back to timed...');
 832 |         const toTimedResult = await client.callTool({
 833 |           name: 'update-event',
 834 |           arguments: {
 835 |             calendarId: TEST_CALENDAR_ID,
 836 |             eventId: eventId,
 837 |             start: '2025-10-25T09:00:00',
 838 |             end: '2025-10-25T10:00:00',
 839 |             timeZone: 'America/Los_Angeles',
 840 |             sendUpdates: SEND_UPDATES
 841 |           }
 842 |         });
 843 | 
 844 |         expect(TestDataFactory.validateEventResponse(toTimedResult)).toBe(true);
 845 |         const timedResponse = JSON.parse((toTimedResult.content as any)[0].text);
 846 |         expect(timedResponse.event).toBeDefined();
 847 |         expect(timedResponse.event.start.dateTime).toBeDefined();
 848 |         expect(timedResponse.event.end.dateTime).toBeDefined();
 849 |         expect(timedResponse.event.start.date).toBeUndefined();
 850 |         console.log('✅ Successfully converted back to timed event');
 851 | 
 852 |         // Step 4: Verify we can create an all-day event directly and convert it
 853 |         console.log('🔄 Testing direct all-day event creation and conversion...');
 854 |         const allDayEventData = {
 855 |           summary: `All-Day Conversion Test ${Date.now()}`,
 856 |           start: '2025-12-25',
 857 |           end: '2025-12-26',
 858 |           description: 'Testing all-day to timed conversion'
 859 |         };
 860 | 
 861 |         const allDayEventId = await createTestEvent(allDayEventData);
 862 |         createdEventIds.push(allDayEventId);
 863 |         console.log(`✅ Created all-day event: ${allDayEventId}`);
 864 | 
 865 |         // Wait for event to be created
 866 |         await new Promise(resolve => setTimeout(resolve, 2000));
 867 | 
 868 |         // Convert all-day to timed
 869 |         const directConversionResult = await client.callTool({
 870 |           name: 'update-event',
 871 |           arguments: {
 872 |             calendarId: TEST_CALENDAR_ID,
 873 |             eventId: allDayEventId,
 874 |             start: '2025-12-25T10:00:00',
 875 |             end: '2025-12-25T17:00:00',
 876 |             timeZone: 'America/Los_Angeles',
 877 |             sendUpdates: SEND_UPDATES
 878 |           }
 879 |         });
 880 | 
 881 |         expect(TestDataFactory.validateEventResponse(directConversionResult)).toBe(true);
 882 |         const directResponse = JSON.parse((directConversionResult.content as any)[0].text);
 883 |         expect(directResponse.event).toBeDefined();
 884 |         expect(directResponse.event.start.dateTime).toBeDefined();
 885 |         expect(directResponse.event.end.dateTime).toBeDefined();
 886 |         console.log('✅ Successfully converted all-day event to timed event');
 887 | 
 888 |         console.log('✨ All conversion tests passed!');
 889 |       });
 890 |     });
 891 | 
 892 |     describe('Batch and Multi-Calendar Operations', () => {
 893 |       it('should handle multiple calendar queries', async () => {
 894 |         const startTime = testFactory.startTimer('list-events-multiple-calendars');
 895 |         
 896 |         try {
 897 |           const timeRanges = TestDataFactory.getTimeRanges();
 898 |           const result = await client.callTool({
 899 |             name: 'list-events',
 900 |             arguments: {
 901 |               calendarId: JSON.stringify(['primary', TEST_CALENDAR_ID]),
 902 |               timeMin: timeRanges.nextWeek.timeMin,
 903 |               timeMax: timeRanges.nextWeek.timeMax
 904 |             }
 905 |           });
 906 |           
 907 |           testFactory.endTimer('list-events-multiple-calendars', startTime, true);
 908 |           expect(TestDataFactory.validateEventResponse(result)).toBe(true);
 909 |         } catch (error) {
 910 |           testFactory.endTimer('list-events-multiple-calendars', startTime, false, String(error));
 911 |           throw error;
 912 |         }
 913 |       });
 914 | 
 915 |       it('should list events with specific fields', async () => {
 916 |         // Create an event with various fields
 917 |         const eventData = TestDataFactory.createEventWithAttendees({
 918 |           summary: `Integration Test - Field Filtering ${Date.now()}`,
 919 |           description: 'Testing field filtering in list-events',
 920 |           location: 'Conference Room A'
 921 |         });
 922 |         
 923 |         const eventId = await createTestEvent(eventData);
 924 |         createdEventIds.push(eventId);
 925 |         
 926 |         const startTime = testFactory.startTimer('list-events-with-fields');
 927 |         
 928 |         try {
 929 |           const timeRanges = TestDataFactory.getTimeRanges();
 930 |           const result = await client.callTool({
 931 |             name: 'list-events',
 932 |             arguments: {
 933 |               calendarId: TEST_CALENDAR_ID,
 934 |               timeMin: timeRanges.nextWeek.timeMin,
 935 |               timeMax: timeRanges.nextWeek.timeMax,
 936 |               fields: ['description', 'location', 'attendees', 'created', 'updated', 'creator', 'organizer']
 937 |             }
 938 |           });
 939 |           
 940 |           testFactory.endTimer('list-events-with-fields', startTime, true);
 941 |           
 942 |           expect(TestDataFactory.validateEventResponse(result)).toBe(true);
 943 |           const responseText = (result.content as any)[0].text;
 944 |           expect(responseText).toContain(eventId);
 945 |           expect(responseText).toContain(eventData.summary);
 946 |           // The response should include the additional fields we requested
 947 |           expect(responseText).toContain(eventData.description!);
 948 |           expect(responseText).toContain(eventData.location!);
 949 |         } catch (error) {
 950 |           testFactory.endTimer('list-events-with-fields', startTime, false, String(error));
 951 |           throw error;
 952 |         }
 953 |       });
 954 | 
 955 |       it('should filter events by extended properties', async () => {
 956 |         // Create two events - one with matching properties, one without
 957 |         const matchingEventData = TestDataFactory.createSingleEvent({
 958 |           summary: `Integration Test - Matching Extended Props ${Date.now()}`
 959 |         });
 960 |         
 961 |         const nonMatchingEventData = TestDataFactory.createSingleEvent({
 962 |           summary: `Integration Test - Non-Matching Extended Props ${Date.now()}`
 963 |         });
 964 |         
 965 |         // Create event with extended properties
 966 |         const result1 = await client.callTool({
 967 |           name: 'create-event',
 968 |           arguments: {
 969 |             calendarId: TEST_CALENDAR_ID,
 970 |             ...matchingEventData,
 971 |             extendedProperties: {
 972 |               private: {
 973 |                 testRun: 'integration-test',
 974 |                 environment: 'test'
 975 |               },
 976 |               shared: {
 977 |                 visibility: 'team'
 978 |               }
 979 |             }
 980 |           }
 981 |         });
 982 |         
 983 |         const matchingEventId = extractEventId(result1);
 984 |         createdEventIds.push(matchingEventId!);
 985 |         
 986 |         // Create event without matching properties
 987 |         const result2 = await client.callTool({
 988 |           name: 'create-event',
 989 |           arguments: {
 990 |             calendarId: TEST_CALENDAR_ID,
 991 |             ...nonMatchingEventData,
 992 |             extendedProperties: {
 993 |               private: {
 994 |                 testRun: 'other-test',
 995 |                 environment: 'production'
 996 |               }
 997 |             }
 998 |           }
 999 |         });
1000 |         
1001 |         const nonMatchingEventId = extractEventId(result2);
1002 |         createdEventIds.push(nonMatchingEventId!);
1003 |         
1004 |         // Wait for events to be searchable
1005 |         await new Promise(resolve => setTimeout(resolve, 1000));
1006 |         
1007 |         const startTime = testFactory.startTimer('list-events-extended-properties');
1008 |         
1009 |         try {
1010 |           const timeRanges = TestDataFactory.getTimeRanges();
1011 |           const result = await client.callTool({
1012 |             name: 'list-events',
1013 |             arguments: {
1014 |               calendarId: TEST_CALENDAR_ID,
1015 |               timeMin: timeRanges.nextWeek.timeMin,
1016 |               timeMax: timeRanges.nextWeek.timeMax,
1017 |               privateExtendedProperty: ['testRun=integration-test', 'environment=test'],
1018 |               sharedExtendedProperty: ['visibility=team']
1019 |             }
1020 |           });
1021 |           
1022 |           testFactory.endTimer('list-events-extended-properties', startTime, true);
1023 |           
1024 |           expect(TestDataFactory.validateEventResponse(result)).toBe(true);
1025 |           const responseText = (result.content as any)[0].text;
1026 |           
1027 |           // Should find the matching event
1028 |           expect(responseText).toContain(matchingEventId);
1029 |           expect(responseText).toContain('Matching Extended Props');
1030 |           
1031 |           // Should NOT find the non-matching event
1032 |           expect(responseText).not.toContain(nonMatchingEventId);
1033 |           expect(responseText).not.toContain('Non-Matching Extended Props');
1034 |         } catch (error) {
1035 |           testFactory.endTimer('list-events-extended-properties', startTime, false, String(error));
1036 |           throw error;
1037 |         }
1038 |       });
1039 | 
1040 |       it('should resolve calendar names to IDs automatically', async () => {
1041 |         const startTime = testFactory.startTimer('list-events-calendar-name-resolution');
1042 | 
1043 |         try {
1044 |           // First, get the list of calendars to find a calendar name
1045 |           const calendarsResult = await client.callTool({
1046 |             name: 'list-calendars',
1047 |             arguments: {}
1048 |           });
1049 | 
1050 |           expect(TestDataFactory.validateEventResponse(calendarsResult)).toBe(true);
1051 |           const calendarsResponse = JSON.parse((calendarsResult.content as any)[0].text);
1052 |           expect(calendarsResponse.calendars).toBeDefined();
1053 |           expect(calendarsResponse.calendars.length).toBeGreaterThan(0);
1054 | 
1055 |           // Get the first calendar's name (summary field)
1056 |           const firstCalendar = calendarsResponse.calendars[0];
1057 |           const calendarName = firstCalendar.summary;
1058 |           const calendarId = firstCalendar.id;
1059 | 
1060 |           console.log(`🔍 Testing calendar name resolution: "${calendarName}" -> "${calendarId}"`);
1061 | 
1062 |           // Test 1: Use calendar name instead of ID
1063 |           const timeRanges = TestDataFactory.getTimeRanges();
1064 |           const resultWithName = await client.callTool({
1065 |             name: 'list-events',
1066 |             arguments: {
1067 |               calendarId: calendarName,  // Using calendar name, not ID
1068 |               timeMin: timeRanges.nextWeek.timeMin,
1069 |               timeMax: timeRanges.nextWeek.timeMax
1070 |             }
1071 |           });
1072 | 
1073 |           expect(TestDataFactory.validateEventResponse(resultWithName)).toBe(true);
1074 |           const responseWithName = JSON.parse((resultWithName.content as any)[0].text);
1075 |           console.log(`✅ Successfully listed events using calendar name: "${calendarName}"`);
1076 | 
1077 |           // Test 2: Use calendar ID directly (for comparison)
1078 |           const resultWithId = await client.callTool({
1079 |             name: 'list-events',
1080 |             arguments: {
1081 |               calendarId: calendarId,
1082 |               timeMin: timeRanges.nextWeek.timeMin,
1083 |               timeMax: timeRanges.nextWeek.timeMax
1084 |             }
1085 |           });
1086 | 
1087 |           expect(TestDataFactory.validateEventResponse(resultWithId)).toBe(true);
1088 |           const responseWithId = JSON.parse((resultWithId.content as any)[0].text);
1089 | 
1090 |           // Both methods should return the same events
1091 |           expect(responseWithName.totalCount).toBe(responseWithId.totalCount);
1092 |           console.log(`✅ Calendar name and ID both return ${responseWithId.totalCount} events`);
1093 | 
1094 |           // Test 3: Use multiple calendar names in an array
1095 |           if (calendarsResponse.calendars.length > 1) {
1096 |             const secondCalendar = calendarsResponse.calendars[1];
1097 |             const calendarNames = [calendarName, secondCalendar.summary];
1098 | 
1099 |             console.log(`🔍 Testing multiple calendar names: ${JSON.stringify(calendarNames)}`);
1100 | 
1101 |             const resultWithMultipleNames = await client.callTool({
1102 |               name: 'list-events',
1103 |               arguments: {
1104 |                 calendarId: JSON.stringify(calendarNames),
1105 |                 timeMin: timeRanges.nextWeek.timeMin,
1106 |                 timeMax: timeRanges.nextWeek.timeMax
1107 |               }
1108 |             });
1109 | 
1110 |             expect(TestDataFactory.validateEventResponse(resultWithMultipleNames)).toBe(true);
1111 |             const responseWithMultipleNames = JSON.parse((resultWithMultipleNames.content as any)[0].text);
1112 |             console.log(`✅ Successfully listed events from ${calendarNames.length} calendars using names`);
1113 |             expect(responseWithMultipleNames.calendars).toBeDefined();
1114 |             expect(responseWithMultipleNames.calendars.length).toBe(2);
1115 |           }
1116 | 
1117 |           // Test 4: Invalid calendar name should provide helpful error
1118 |           // Note: MCP tools return errors as responses (with error content), not as thrown exceptions
1119 |           const result = await client.callTool({
1120 |             name: 'list-events',
1121 |             arguments: {
1122 |               calendarId: 'ThisCalendarNameDefinitelyDoesNotExist_XYZ123',
1123 |               timeMin: timeRanges.nextWeek.timeMin,
1124 |               timeMax: timeRanges.nextWeek.timeMax
1125 |             }
1126 |           });
1127 | 
1128 |           // Extract the error message from the MCP response
1129 |           const resultText = (result.content as any)[0]?.text || JSON.stringify(result);
1130 | 
1131 |           // Verify it contains our resolution error message
1132 |           expect(resultText).toContain('Calendar(s) not found');
1133 |           expect(resultText).toContain('ThisCalendarNameDefinitelyDoesNotExist_XYZ123');
1134 |           expect(resultText).toContain('Available calendars');
1135 |           console.log('✅ Helpful error message provided for invalid calendar name');
1136 |           console.log(`   Error: ${resultText.substring(0, 150)}...`);
1137 | 
1138 |           testFactory.endTimer('list-events-calendar-name-resolution', startTime, true);
1139 |         } catch (error) {
1140 |           testFactory.endTimer('list-events-calendar-name-resolution', startTime, false, String(error));
1141 |           throw error;
1142 |         }
1143 |       });
1144 | 
1145 |       it('should search events with specific fields', async () => {
1146 |         // Create an event with rich data
1147 |         const eventData = TestDataFactory.createColoredEvent('11', {
1148 |           summary: `Search Test - Field Filtering Event ${Date.now()}`,
1149 |           description: 'This event tests field filtering in search-events',
1150 |           location: 'Virtual Meeting Room'
1151 |         });
1152 |         
1153 |         const eventId = await createTestEvent(eventData);
1154 |         createdEventIds.push(eventId);
1155 |         
1156 |         // Wait for event to be searchable
1157 |         await new Promise(resolve => setTimeout(resolve, 2000));
1158 |         
1159 |         const startTime = testFactory.startTimer('search-events-with-fields');
1160 |         
1161 |         try {
1162 |           const timeRanges = TestDataFactory.getTimeRanges();
1163 |           const result = await client.callTool({
1164 |             name: 'search-events',
1165 |             arguments: {
1166 |               calendarId: TEST_CALENDAR_ID,
1167 |               query: 'Field Filtering',
1168 |               timeMin: timeRanges.nextWeek.timeMin,
1169 |               timeMax: timeRanges.nextWeek.timeMax,
1170 |               fields: ['colorId', 'description', 'location', 'created', 'updated', 'htmlLink']
1171 |             }
1172 |           });
1173 |           
1174 |           testFactory.endTimer('search-events-with-fields', startTime, true);
1175 |           
1176 |           expect(TestDataFactory.validateEventResponse(result)).toBe(true);
1177 |           const responseText = (result.content as any)[0].text;
1178 |           expect(responseText).toContain(eventId);
1179 |           expect(responseText).toContain(eventData.summary);
1180 |           expect(responseText).toContain(eventData.description!);
1181 |           expect(responseText).toContain(eventData.location!);
1182 |           // Color information may not be included when specific fields are requested
1183 |         // Just verify the search found the event with the requested fields
1184 |         } catch (error) {
1185 |           testFactory.endTimer('search-events-with-fields', startTime, false, String(error));
1186 |           throw error;
1187 |         }
1188 |       });
1189 | 
1190 |       it('should search events filtered by extended properties', async () => {
1191 |         // Create event with searchable content and extended properties
1192 |         const uniqueId = Date.now();
1193 |         const eventData = TestDataFactory.createSingleEvent({
1194 |           summary: `Search Extended Props Test Event ${uniqueId}`,
1195 |           description: 'This event has extended properties for filtering'
1196 |         });
1197 | 
1198 |         const result = await client.callTool({
1199 |           name: 'create-event',
1200 |           arguments: {
1201 |             calendarId: TEST_CALENDAR_ID,
1202 |             ...eventData,
1203 |             allowDuplicates: true, // Add this to handle duplicate events from previous runs
1204 |             extendedProperties: {
1205 |               private: {
1206 |                 searchTest: `enabled-${uniqueId}`,
1207 |                 category: 'integration'
1208 |               },
1209 |               shared: {
1210 |                 team: 'qa'
1211 |               }
1212 |             }
1213 |           }
1214 |         });
1215 | 
1216 |         const eventId = extractEventId(result);
1217 |         expect(eventId).toBeTruthy(); // Make sure we got an event ID
1218 |         createdEventIds.push(eventId!);
1219 | 
1220 |         // Wait for event to be searchable
1221 |         await new Promise(resolve => setTimeout(resolve, 2000));
1222 | 
1223 |         const startTime = testFactory.startTimer('search-events-extended-properties');
1224 | 
1225 |         try {
1226 |           const timeRanges = TestDataFactory.getTimeRanges();
1227 |           const searchResult = await client.callTool({
1228 |             name: 'search-events',
1229 |             arguments: {
1230 |               calendarId: TEST_CALENDAR_ID,
1231 |               query: 'Extended Props',
1232 |               timeMin: timeRanges.nextWeek.timeMin,
1233 |               timeMax: timeRanges.nextWeek.timeMax,
1234 |               privateExtendedProperty: [`searchTest=enabled-${uniqueId}`, 'category=integration'],
1235 |               sharedExtendedProperty: ['team=qa']
1236 |             }
1237 |           });
1238 |           
1239 |           testFactory.endTimer('search-events-extended-properties', startTime, true);
1240 |           
1241 |           expect(TestDataFactory.validateEventResponse(searchResult)).toBe(true);
1242 |           const response = JSON.parse((searchResult.content as any)[0].text);
1243 |           expect(response.events).toBeDefined();
1244 |           expect(response.events.length).toBeGreaterThan(0);
1245 |           expect(response.events[0].id).toBe(eventId);
1246 |           expect(response.events[0].summary).toContain('Search Extended Props Test Event');
1247 |         } catch (error) {
1248 |           testFactory.endTimer('search-events-extended-properties', startTime, false, String(error));
1249 |           throw error;
1250 |         }
1251 |       });
1252 |     });
1253 | 
1254 |     describe('Free/Busy Queries', () => {
1255 |       it('should check availability for test calendar', async () => {
1256 |         const startTime = testFactory.startTimer('get-freebusy');
1257 | 
1258 |         try {
1259 |           const timeRanges = TestDataFactory.getTimeRanges();
1260 |           const result = await client.callTool({
1261 |             name: 'get-freebusy',
1262 |             arguments: {
1263 |               calendars: [{ id: TEST_CALENDAR_ID }],
1264 |               timeMin: timeRanges.nextWeek.timeMin,
1265 |               timeMax: timeRanges.nextWeek.timeMax,
1266 |               timeZone: 'America/Los_Angeles'
1267 |             }
1268 |           });
1269 | 
1270 |           testFactory.endTimer('get-freebusy', startTime, true);
1271 |           expect(TestDataFactory.validateEventResponse(result)).toBe(true);
1272 | 
1273 |           const response = JSON.parse((result.content as any)[0].text);
1274 |           expect(response.timeMin).toBeDefined();
1275 |           expect(response.timeMax).toBeDefined();
1276 |           expect(response.calendars).toBeDefined();
1277 |           expect(typeof response.calendars).toBe('object');
1278 |         } catch (error) {
1279 |           testFactory.endTimer('get-freebusy', startTime, false, String(error));
1280 |           throw error;
1281 |         }
1282 |       });
1283 | 
1284 |       it('should create event with custom event ID', async () => {
1285 |         // Google Calendar event IDs must use base32hex encoding: lowercase a-v and 0-9 only
1286 |         // Generate a valid base32hex ID
1287 |         const timestamp = Date.now().toString(32).replace(/[w-z]/g, (c) => 
1288 |           String.fromCharCode(c.charCodeAt(0) - 22)
1289 |         );
1290 |         const randomPart = Math.random().toString(32).substring(2, 8).replace(/[w-z]/g, (c) => 
1291 |           String.fromCharCode(c.charCodeAt(0) - 22)
1292 |         );
1293 |         const customEventId = `test${timestamp}${randomPart}`.substring(0, 26);
1294 |         
1295 |         const eventData = TestDataFactory.createSingleEvent({
1296 |           summary: `Integration Test - Custom Event ID ${Date.now()}`
1297 |         });
1298 |         
1299 |         const startTime = testFactory.startTimer('create-event-custom-id');
1300 |         
1301 |         try {
1302 |           const result = await client.callTool({
1303 |             name: 'create-event',
1304 |             arguments: {
1305 |               calendarId: TEST_CALENDAR_ID,
1306 |               eventId: customEventId,
1307 |               ...eventData
1308 |             }
1309 |           });
1310 |           
1311 |           testFactory.endTimer('create-event-custom-id', startTime, true);
1312 |           
1313 |           expect(TestDataFactory.validateEventResponse(result)).toBe(true);
1314 |           
1315 |           const responseText = (result.content as any)[0].text;
1316 |           expect(responseText).toContain(customEventId);
1317 |           
1318 |           // Clean up
1319 |           createdEventIds.push(customEventId);
1320 |           testFactory.addCreatedEventId(customEventId);
1321 |         } catch (error) {
1322 |           testFactory.endTimer('create-event-custom-id', startTime, false, String(error));
1323 |           throw error;
1324 |         }
1325 |       });
1326 | 
1327 |       it('should handle duplicate custom event ID error', async () => {
1328 |         // Google Calendar event IDs must use base32hex encoding: lowercase a-v and 0-9 only
1329 |         // Generate a valid base32hex ID
1330 |         const timestamp = Date.now().toString(32).replace(/[w-z]/g, (c) => 
1331 |           String.fromCharCode(c.charCodeAt(0) - 22)
1332 |         );
1333 |         const randomPart = Math.random().toString(32).substring(2, 8).replace(/[w-z]/g, (c) => 
1334 |           String.fromCharCode(c.charCodeAt(0) - 22)
1335 |         );
1336 |         const customEventId = `dup${timestamp}${randomPart}`.substring(0, 26);
1337 |         
1338 |         const eventData = TestDataFactory.createSingleEvent({
1339 |           summary: `Integration Test - Duplicate ID Test ${Date.now()}`
1340 |         });
1341 |         
1342 |         // First create an event with custom ID
1343 |         const result1 = await client.callTool({
1344 |           name: 'create-event',
1345 |           arguments: {
1346 |             calendarId: TEST_CALENDAR_ID,
1347 |             eventId: customEventId,
1348 |             ...eventData
1349 |           }
1350 |         });
1351 |         
1352 |         expect(TestDataFactory.validateEventResponse(result1)).toBe(true);
1353 |         createdEventIds.push(customEventId);
1354 |         
1355 |         // Wait a moment for Google Calendar to fully process the event
1356 |         await new Promise(resolve => setTimeout(resolve, 1000));
1357 |         
1358 |         // Try to create another event with the same ID
1359 |         const startTime = testFactory.startTimer('create-event-duplicate-id');
1360 |         
1361 |         try {
1362 |           await client.callTool({
1363 |             name: 'create-event',
1364 |             arguments: {
1365 |               calendarId: TEST_CALENDAR_ID,
1366 |               eventId: customEventId,
1367 |               ...eventData
1368 |             }
1369 |           });
1370 |           
1371 |           // If we get here, the duplicate wasn't caught (test should fail)
1372 |           testFactory.endTimer('create-event-duplicate-id', startTime, false);
1373 |           expect.fail('Expected error for duplicate event ID');
1374 |         } catch (error: any) {
1375 |           testFactory.endTimer('create-event-duplicate-id', startTime, true);
1376 |           
1377 |           // The error should mention the ID already exists
1378 |           const errorMessage = error.message || String(error);
1379 |           expect(errorMessage).toMatch(/already exists|duplicate|conflict|409/i);
1380 |         }
1381 |       });
1382 | 
1383 |       it('should create event with transparency and visibility options', async () => {
1384 |         const eventData = TestDataFactory.createSingleEvent({
1385 |           summary: `Integration Test - Transparency and Visibility ${Date.now()}`
1386 |         });
1387 |         
1388 |         const startTime = testFactory.startTimer('create-event-transparency-visibility');
1389 |         
1390 |         try {
1391 |           const result = await client.callTool({
1392 |             name: 'create-event',
1393 |             arguments: {
1394 |               calendarId: TEST_CALENDAR_ID,
1395 |               ...eventData,
1396 |               transparency: 'transparent',
1397 |               visibility: 'private',
1398 |               guestsCanInviteOthers: false,
1399 |               guestsCanModify: true,
1400 |               guestsCanSeeOtherGuests: false
1401 |             }
1402 |           });
1403 |           
1404 |           testFactory.endTimer('create-event-transparency-visibility', startTime, true);
1405 |           
1406 |           expect(TestDataFactory.validateEventResponse(result)).toBe(true);
1407 |           
1408 |           const eventId = extractEventId(result);
1409 |           expect(eventId).toBeTruthy();
1410 |           
1411 |           createdEventIds.push(eventId!);
1412 |           testFactory.addCreatedEventId(eventId!);
1413 |         } catch (error) {
1414 |           testFactory.endTimer('create-event-transparency-visibility', startTime, false, String(error));
1415 |           throw error;
1416 |         }
1417 |       });
1418 | 
1419 |       it('should create event with extended properties', async () => {
1420 |         const eventData = TestDataFactory.createSingleEvent({
1421 |           summary: `Integration Test - Extended Properties ${Date.now()}`
1422 |         });
1423 |         
1424 |         const startTime = testFactory.startTimer('create-event-extended-properties');
1425 |         
1426 |         try {
1427 |           const result = await client.callTool({
1428 |             name: 'create-event',
1429 |             arguments: {
1430 |               calendarId: TEST_CALENDAR_ID,
1431 |               ...eventData,
1432 |               extendedProperties: {
1433 |                 private: {
1434 |                   projectId: 'proj-123',
1435 |                   customerId: 'cust-456',
1436 |                   category: 'meeting'
1437 |                 },
1438 |                 shared: {
1439 |                   department: 'engineering',
1440 |                   team: 'backend'
1441 |                 }
1442 |               }
1443 |             }
1444 |           });
1445 |           
1446 |           testFactory.endTimer('create-event-extended-properties', startTime, true);
1447 |           
1448 |           expect(TestDataFactory.validateEventResponse(result)).toBe(true);
1449 |           
1450 |           const eventId = extractEventId(result);
1451 |           expect(eventId).toBeTruthy();
1452 |           
1453 |           createdEventIds.push(eventId!);
1454 |           testFactory.addCreatedEventId(eventId!);
1455 |           
1456 |           // Verify the event can be found by extended properties
1457 |           await new Promise(resolve => setTimeout(resolve, 1000));
1458 |           
1459 |           const searchResult = await client.callTool({
1460 |             name: 'list-events',
1461 |             arguments: {
1462 |               calendarId: TEST_CALENDAR_ID,
1463 |               timeMin: eventData.start,
1464 |               timeMax: eventData.end,
1465 |               privateExtendedProperty: ['projectId=proj-123', 'customerId=cust-456']
1466 |             }
1467 |           });
1468 |           
1469 |           expect(TestDataFactory.validateEventResponse(searchResult)).toBe(true);
1470 |           const searchResponse = JSON.parse((searchResult.content as any)[0].text);
1471 |           expect(searchResponse.events).toBeDefined();
1472 |           const foundEvent = searchResponse.events.find((e: any) => e.id === eventId);
1473 |           expect(foundEvent).toBeDefined();
1474 |         } catch (error) {
1475 |           testFactory.endTimer('create-event-extended-properties', startTime, false, String(error));
1476 |           throw error;
1477 |         }
1478 |       });
1479 | 
1480 |       it('should create event with conference data', async () => {
1481 |         const eventData = TestDataFactory.createSingleEvent({
1482 |           summary: `Integration Test - Conference Event ${Date.now()}`
1483 |         });
1484 |         
1485 |         const startTime = testFactory.startTimer('create-event-conference');
1486 |         
1487 |         try {
1488 |           const result = await client.callTool({
1489 |             name: 'create-event',
1490 |             arguments: {
1491 |               calendarId: TEST_CALENDAR_ID,
1492 |               ...eventData,
1493 |               conferenceData: {
1494 |                 createRequest: {
1495 |                   requestId: `conf-${Date.now()}`,
1496 |                   conferenceSolutionKey: {
1497 |                     type: 'hangoutsMeet'
1498 |                   }
1499 |                 }
1500 |               }
1501 |             }
1502 |           });
1503 |           
1504 |           testFactory.endTimer('create-event-conference', startTime, true);
1505 |           
1506 |           expect(TestDataFactory.validateEventResponse(result)).toBe(true);
1507 |           
1508 |           const eventId = extractEventId(result);
1509 |           expect(eventId).toBeTruthy();
1510 |           
1511 |           createdEventIds.push(eventId!);
1512 |           testFactory.addCreatedEventId(eventId!);
1513 |         } catch (error) {
1514 |           testFactory.endTimer('create-event-conference', startTime, false, String(error));
1515 |           throw error;
1516 |         }
1517 |       });
1518 | 
1519 |       it('should create event with source information', async () => {
1520 |         const eventData = TestDataFactory.createSingleEvent({
1521 |           summary: `Integration Test - Event with Source ${Date.now()}`
1522 |         });
1523 |         
1524 |         const startTime = testFactory.startTimer('create-event-source');
1525 |         
1526 |         try {
1527 |           const result = await client.callTool({
1528 |             name: 'create-event',
1529 |             arguments: {
1530 |               calendarId: TEST_CALENDAR_ID,
1531 |               ...eventData,
1532 |               source: {
1533 |                 url: 'https://example.com/events/123',
1534 |                 title: 'Original Event Source'
1535 |               }
1536 |             }
1537 |           });
1538 |           
1539 |           testFactory.endTimer('create-event-source', startTime, true);
1540 |           
1541 |           expect(TestDataFactory.validateEventResponse(result)).toBe(true);
1542 |           
1543 |           const eventId = extractEventId(result);
1544 |           expect(eventId).toBeTruthy();
1545 |           
1546 |           createdEventIds.push(eventId!);
1547 |           testFactory.addCreatedEventId(eventId!);
1548 |         } catch (error) {
1549 |           testFactory.endTimer('create-event-source', startTime, false, String(error));
1550 |           throw error;
1551 |         }
1552 |       });
1553 | 
1554 |       it('should create event with complex attendee details', async () => {
1555 |         const eventData = TestDataFactory.createSingleEvent({
1556 |           summary: `Integration Test - Complex Attendees ${Date.now()}`
1557 |         });
1558 |         
1559 |         const startTime = testFactory.startTimer('create-event-complex-attendees');
1560 |         
1561 |         try {
1562 |           const result = await client.callTool({
1563 |             name: 'create-event',
1564 |             arguments: {
1565 |               calendarId: TEST_CALENDAR_ID,
1566 |               ...eventData,
1567 |               attendees: [
1568 |                 {
1569 |                   email: '[email protected]',
1570 |                   displayName: 'Required Attendee',
1571 |                   optional: false,
1572 |                   responseStatus: 'needsAction',
1573 |                   comment: 'Looking forward to the meeting',
1574 |                   additionalGuests: 2
1575 |                 },
1576 |                 {
1577 |                   email: '[email protected]',
1578 |                   displayName: 'Optional Attendee',
1579 |                   optional: true,
1580 |                   responseStatus: 'tentative'
1581 |                 }
1582 |               ],
1583 |               sendUpdates: 'none' // Don't send real emails in tests
1584 |             }
1585 |           });
1586 |           
1587 |           testFactory.endTimer('create-event-complex-attendees', startTime, true);
1588 |           
1589 |           expect(TestDataFactory.validateEventResponse(result)).toBe(true);
1590 |           
1591 |           const eventId = extractEventId(result);
1592 |           expect(eventId).toBeTruthy();
1593 |           
1594 |           createdEventIds.push(eventId!);
1595 |           testFactory.addCreatedEventId(eventId!);
1596 |         } catch (error) {
1597 |           testFactory.endTimer('create-event-complex-attendees', startTime, false, String(error));
1598 |           throw error;
1599 |         }
1600 |       });
1601 |     });
1602 |   });
1603 | 
1604 |   describe('Error Handling and Edge Cases', () => {
1605 |     it('should handle invalid calendar ID gracefully', async () => {
1606 |       const invalidData = TestDataFactory.getInvalidTestData();
1607 |       const now = new Date();
1608 |       const tomorrow = new Date(now.getTime() + 24 * 60 * 60 * 1000);
1609 |       
1610 |       try {
1611 |         await client.callTool({
1612 |           name: 'list-events',
1613 |           arguments: {
1614 |             calendarId: invalidData.invalidCalendarId,
1615 |             timeMin: TestDataFactory.formatDateTimeRFC3339WithTimezone(now),
1616 |             timeMax: TestDataFactory.formatDateTimeRFC3339WithTimezone(tomorrow)
1617 |           }
1618 |         });
1619 |         
1620 |         // If we get here, the error wasn't caught (test should fail)
1621 |         expect.fail('Expected error for invalid calendar ID');
1622 |       } catch (error: any) {
1623 |         // Should get an error about invalid calendar ID
1624 |         const errorMessage = error.message || String(error);
1625 |         expect(errorMessage.toLowerCase()).toContain('error');
1626 |       }
1627 |     });
1628 | 
1629 |     it('should handle invalid event ID gracefully', async () => {
1630 |       const invalidData = TestDataFactory.getInvalidTestData();
1631 |       
1632 |       try {
1633 |         await client.callTool({
1634 |           name: 'delete-event',
1635 |           arguments: {
1636 |             calendarId: TEST_CALENDAR_ID,
1637 |             eventId: invalidData.invalidEventId,
1638 |             sendUpdates: SEND_UPDATES
1639 |           }
1640 |         });
1641 |         
1642 |         // If we get here, the error wasn't caught (test should fail)
1643 |         expect.fail('Expected error for invalid event ID');
1644 |       } catch (error: any) {
1645 |         // Should get an error about invalid event ID
1646 |         const errorMessage = error.message || String(error);
1647 |         expect(errorMessage.toLowerCase()).toContain('error');
1648 |       }
1649 |     });
1650 | 
1651 |     it('should handle malformed date formats gracefully', async () => {
1652 |       const invalidData = TestDataFactory.getInvalidTestData();
1653 |       
1654 |       try {
1655 |         await client.callTool({
1656 |           name: 'create-event',
1657 |           arguments: {
1658 |             calendarId: TEST_CALENDAR_ID,
1659 |             summary: 'Test Event',
1660 |             start: invalidData.invalidTimeFormat,
1661 |             end: invalidData.invalidTimeFormat,
1662 |             timeZone: 'America/Los_Angeles',
1663 |             sendUpdates: SEND_UPDATES
1664 |           }
1665 |         });
1666 |         
1667 |         // If we get here, the error wasn't caught (test should fail)
1668 |         expect.fail('Expected error for malformed date format');
1669 |       } catch (error: any) {
1670 |         // Should get an error about invalid time value
1671 |         const errorMessage = error.message || String(error);
1672 |         expect(errorMessage.toLowerCase()).toMatch(/invalid|error|time/i);
1673 |       }
1674 |     });
1675 |   });
1676 | 
1677 |   describe('Timezone Handling Validation', () => {
1678 |     it('should correctly interpret timezone-naive timeMin/timeMax in specified timezone', async () => {
1679 |       // Test scenario: Create an event at 10:00 AM Los Angeles time,
1680 |       // then use list-events with timezone-naive timeMin/timeMax and explicit timeZone
1681 |       // to verify the event is found within a narrow time window.
1682 |       
1683 |       console.log('🧪 Testing timezone interpretation fix...');
1684 |       
1685 |       // Step 1: Create an event at 10:00 AM Los Angeles time on a specific date
1686 |       const testDate = new Date();
1687 |       testDate.setDate(testDate.getDate() + 7); // Next week to avoid conflicts
1688 |       const year = testDate.getFullYear();
1689 |       const month = String(testDate.getMonth() + 1).padStart(2, '0');
1690 |       const day = String(testDate.getDate()).padStart(2, '0');
1691 |       
1692 |       const eventStart = `${year}-${month}-${day}T10:00:00-08:00`; // 10:00 AM PST (or PDT)
1693 |       const eventEnd = `${year}-${month}-${day}T11:00:00-08:00`;   // 11:00 AM PST (or PDT)
1694 |       
1695 |       const eventData: TestEvent = {
1696 |         summary: 'Timezone Test Event - LA Time',
1697 |         start: eventStart,
1698 |         end: eventEnd,
1699 |         description: 'This event tests timezone interpretation in list-events calls',
1700 |         timeZone: 'America/Los_Angeles',
1701 |         sendUpdates: SEND_UPDATES
1702 |       };
1703 |       
1704 |       console.log(`📅 Creating event at ${eventStart} (Los Angeles time)`);
1705 |       
1706 |       const eventId = await createTestEvent(eventData);
1707 |       createdEventIds.push(eventId);
1708 |       
1709 |       // Step 2: Use list-events with timezone-naive timeMin/timeMax and explicit timeZone
1710 |       // This should correctly interpret the times as Los Angeles time, not system time
1711 |       
1712 |       // Define a narrow time window that includes our event (9:30 AM - 11:30 AM LA time)
1713 |       const timeMin = `${year}-${month}-${day}T09:30:00`; // Timezone-naive
1714 |       const timeMax = `${year}-${month}-${day}T11:30:00`; // Timezone-naive
1715 |       
1716 |       console.log(`🔍 Searching for event using timezone-naive times: ${timeMin} to ${timeMax} (interpreted as Los Angeles time)`);
1717 |       
1718 |       const startTime = testFactory.startTimer('list-events-timezone-naive');
1719 |       
1720 |       try {
1721 |         const listResult = await client.callTool({
1722 |           name: 'list-events',
1723 |           arguments: {
1724 |             calendarId: TEST_CALENDAR_ID,
1725 |             timeMin: timeMin,
1726 |             timeMax: timeMax,
1727 |             timeZone: 'America/Los_Angeles' // This should interpret the timezone-naive times as LA time
1728 |           }
1729 |         });
1730 |         
1731 |         testFactory.endTimer('list-events-timezone-naive', startTime, true);
1732 |         
1733 |         expect(TestDataFactory.validateEventResponse(listResult)).toBe(true);
1734 |         const responseText = (listResult.content as any)[0].text;
1735 |         
1736 |         // The event should be found because:
1737 |         // - Event is at 10:00-11:00 AM LA time
1738 |         // - Search window is 9:30-11:30 AM LA time (correctly interpreted)
1739 |         expect(responseText).toContain(eventId);
1740 |         expect(responseText).toContain('Timezone Test Event - LA Time');
1741 |         
1742 |         console.log('✅ Event found in timezone-aware search');
1743 |         
1744 |         // Step 3: Test the negative case - narrow window that excludes the event
1745 |         // Search for 8:00-9:00 AM LA time (should NOT find the 10:00 AM event)
1746 |         const excludingTimeMin = `${year}-${month}-${day}T08:00:00`;
1747 |         const excludingTimeMax = `${year}-${month}-${day}T09:00:00`;
1748 |         
1749 |         console.log(`🔍 Testing negative case with excluding time window: ${excludingTimeMin} to ${excludingTimeMax}`);
1750 |         
1751 |         const excludingResult = await client.callTool({
1752 |           name: 'list-events',
1753 |           arguments: {
1754 |             calendarId: TEST_CALENDAR_ID,
1755 |             timeMin: excludingTimeMin,
1756 |             timeMax: excludingTimeMax,
1757 |             timeZone: 'America/Los_Angeles'
1758 |           }
1759 |         });
1760 |         
1761 |         expect(TestDataFactory.validateEventResponse(excludingResult)).toBe(true);
1762 |         const excludingResponseText = (excludingResult.content as any)[0].text;
1763 |         
1764 |         // The event should NOT be found in this time window
1765 |         expect(excludingResponseText).not.toContain(eventId);
1766 |         
1767 |         console.log('✅ Event correctly excluded from non-overlapping time window');
1768 |       } catch (error) {
1769 |         testFactory.endTimer('list-events-timezone-naive', startTime, false, String(error));
1770 |         throw error;
1771 |       }
1772 |     });
1773 |     
1774 |     it('should correctly handle DST transitions in timezone interpretation', async () => {
1775 |       // Test during DST period (July) to ensure DST is handled correctly
1776 |       console.log('🧪 Testing DST timezone interpretation...');
1777 |       
1778 |       // Create an event in July (PDT period)
1779 |       const eventStart = '2024-07-15T10:00:00-07:00'; // 10:00 AM PDT
1780 |       const eventEnd = '2024-07-15T11:00:00-07:00';   // 11:00 AM PDT
1781 |       
1782 |       const eventData: TestEvent = {
1783 |         summary: 'DST Timezone Test Event',
1784 |         start: eventStart,
1785 |         end: eventEnd,
1786 |         description: 'This event tests DST timezone interpretation',
1787 |         timeZone: 'America/Los_Angeles',
1788 |         sendUpdates: SEND_UPDATES
1789 |       };
1790 |       
1791 |       console.log(`📅 Creating DST event at ${eventStart} (Los Angeles PDT)`);
1792 |       
1793 |       const eventId = await createTestEvent(eventData);
1794 |       createdEventIds.push(eventId);
1795 |       
1796 |       const startTime = testFactory.startTimer('list-events-dst');
1797 |       
1798 |       try {
1799 |         // Search with timezone-naive times during DST period
1800 |         const timeMin = '2024-07-15T09:30:00'; // Should be interpreted as PDT
1801 |         const timeMax = '2024-07-15T11:30:00'; // Should be interpreted as PDT
1802 |         
1803 |         console.log(`🔍 Searching during DST period: ${timeMin} to ${timeMax} (PDT)`);
1804 |         
1805 |         const listResult = await client.callTool({
1806 |           name: 'list-events',
1807 |           arguments: {
1808 |             calendarId: TEST_CALENDAR_ID,
1809 |             timeMin: timeMin,
1810 |             timeMax: timeMax,
1811 |             timeZone: 'America/Los_Angeles'
1812 |           }
1813 |         });
1814 |         
1815 |         testFactory.endTimer('list-events-dst', startTime, true);
1816 |         
1817 |         expect(TestDataFactory.validateEventResponse(listResult)).toBe(true);
1818 |         const responseText = (listResult.content as any)[0].text;
1819 |         
1820 |         expect(responseText).toContain(eventId);
1821 |         expect(responseText).toContain('DST Timezone Test Event');
1822 |         
1823 |         console.log('✅ DST timezone interpretation works correctly');
1824 |       } catch (error) {
1825 |         testFactory.endTimer('list-events-dst', startTime, false, String(error));
1826 |         throw error;
1827 |       }
1828 |     });
1829 |     
1830 |     it('should preserve timezone-aware datetime inputs regardless of timeZone parameter', async () => {
1831 |       // Test that when timeMin/timeMax already have timezone info, 
1832 |       // the timeZone parameter doesn't override them
1833 |       console.log('🧪 Testing timezone-aware datetime preservation...');
1834 |       
1835 |       const testDate = new Date();
1836 |       testDate.setDate(testDate.getDate() + 8);
1837 |       const year = testDate.getFullYear();
1838 |       const month = String(testDate.getMonth() + 1).padStart(2, '0');
1839 |       const day = String(testDate.getDate()).padStart(2, '0');
1840 |       
1841 |       // Create event in New York time
1842 |       const eventStart = `${year}-${month}-${day}T14:00:00-05:00`; // 2:00 PM EST
1843 |       const eventEnd = `${year}-${month}-${day}T15:00:00-05:00`;   // 3:00 PM EST
1844 |       
1845 |       const eventData: TestEvent = {
1846 |         summary: 'Timezone-Aware Input Test Event',
1847 |         start: eventStart,
1848 |         end: eventEnd,
1849 |         timeZone: 'America/New_York',
1850 |         sendUpdates: SEND_UPDATES
1851 |       };
1852 |       
1853 |       const eventId = await createTestEvent(eventData);
1854 |       createdEventIds.push(eventId);
1855 |       
1856 |       const startTime = testFactory.startTimer('list-events-timezone-aware');
1857 |       
1858 |       try {
1859 |         // Search using timezone-aware timeMin/timeMax with a different timeZone parameter
1860 |         // The timezone-aware inputs should be preserved, not converted
1861 |         const timeMin = `${year}-${month}-${day}T13:30:00-05:00`; // 1:30 PM EST (timezone-aware)
1862 |         const timeMax = `${year}-${month}-${day}T15:30:00-05:00`; // 3:30 PM EST (timezone-aware)
1863 |         
1864 |         const listResult = await client.callTool({
1865 |           name: 'list-events',
1866 |           arguments: {
1867 |             calendarId: TEST_CALENDAR_ID,
1868 |             timeMin: timeMin,
1869 |             timeMax: timeMax,
1870 |             timeZone: 'America/Los_Angeles' // Different timezone - should be ignored
1871 |           }
1872 |         });
1873 |         
1874 |         testFactory.endTimer('list-events-timezone-aware', startTime, true);
1875 |         
1876 |         expect(TestDataFactory.validateEventResponse(listResult)).toBe(true);
1877 |         const responseText = (listResult.content as any)[0].text;
1878 |         
1879 |         expect(responseText).toContain(eventId);
1880 |         expect(responseText).toContain('Timezone-Aware Input Test Event');
1881 |         
1882 |         console.log('✅ Timezone-aware inputs preserved correctly');
1883 |       } catch (error) {
1884 |         testFactory.endTimer('list-events-timezone-aware', startTime, false, String(error));
1885 |         throw error;
1886 |       }
1887 |     });
1888 |   });
1889 | 
1890 |   describe('Enhanced Conflict Detection', () => {
1891 |     describe('Smart Duplicate Detection with Simplified Algorithm', () => {
1892 |       it('should detect duplicates with rules-based similarity scoring', async () => {
1893 |         // Create base event with fixed time for consistent duplicate detection
1894 |         const fixedStart = new Date();
1895 |         fixedStart.setDate(fixedStart.getDate() + 5); // 5 days from now
1896 |         fixedStart.setHours(14, 0, 0, 0); // 2 PM
1897 |         const fixedEnd = new Date(fixedStart);
1898 |         fixedEnd.setHours(15, 0, 0, 0); // 3 PM
1899 |         
1900 |         // Pre-check: Clear any existing events in this time window
1901 |         const timeRangeStart = new Date(fixedStart);
1902 |         timeRangeStart.setHours(0, 0, 0, 0); // Start of day
1903 |         const timeRangeEnd = new Date(fixedStart);
1904 |         timeRangeEnd.setHours(23, 59, 59, 999); // End of day
1905 |         
1906 |         const existingEventsResult = await client.callTool({
1907 |           name: 'list-events',
1908 |           arguments: {
1909 |             calendarId: TEST_CALENDAR_ID,
1910 |             timeMin: TestDataFactory.formatDateTimeRFC3339(timeRangeStart),
1911 |             timeMax: TestDataFactory.formatDateTimeRFC3339(timeRangeEnd)
1912 |           }
1913 |         });
1914 |         
1915 |         // Delete any existing events found
1916 |         const existingEventIds = TestDataFactory.extractAllEventIds(existingEventsResult);
1917 |         if (existingEventIds.length > 0) {
1918 |           console.log(`🧹 Pre-test cleanup: Removing ${existingEventIds.length} existing events from test time window`);
1919 |           for (const eventId of existingEventIds) {
1920 |             try {
1921 |               await client.callTool({
1922 |                 name: 'delete-event',
1923 |                 arguments: {
1924 |                   calendarId: TEST_CALENDAR_ID,
1925 |                   eventId,
1926 |                   sendUpdates: SEND_UPDATES
1927 |                 }
1928 |               });
1929 |             } catch (error) {
1930 |               // Ignore errors - event might be protected or already deleted
1931 |             }
1932 |           }
1933 |           // Wait for deletions to propagate
1934 |           await new Promise(resolve => setTimeout(resolve, 2000));
1935 |         }
1936 |         
1937 |         const timestamp = Date.now();
1938 |         const baseEvent = TestDataFactory.createSingleEvent({
1939 |           summary: `Team Meeting ${timestamp}`,
1940 |           location: 'Conference Room A',
1941 |           start: TestDataFactory.formatDateTimeRFC3339(fixedStart),
1942 |           end: TestDataFactory.formatDateTimeRFC3339(fixedEnd)
1943 |         });
1944 |         
1945 |         const baseEventId = await createTestEvent(baseEvent);
1946 |         createdEventIds.push(baseEventId);
1947 |         
1948 |         // Note: Google Calendar has eventual consistency - events may not immediately
1949 |         // appear in list queries. This delay helps but doesn't guarantee visibility.
1950 |         await new Promise(resolve => setTimeout(resolve, 3000));
1951 |         
1952 |         // Test 1: Exact title + overlapping time = 95% similarity (blocked)
1953 |         const exactDuplicateResult = await client.callTool({
1954 |           name: 'create-event',
1955 |           arguments: {
1956 |             calendarId: TEST_CALENDAR_ID,
1957 |             ...baseEvent
1958 |           }
1959 |         });
1960 |         
1961 |         // In v2.0, exact duplicates throw an error returned as text
1962 |         const exactDuplicateText = (exactDuplicateResult.content as any)[0]?.text;
1963 |         expect(exactDuplicateText).toContain('Duplicate event detected');
1964 |         
1965 |         // Test 2: Similar title + overlapping time = 70% similarity (warning)
1966 |         const similarTitleEvent = {
1967 |           ...baseEvent,
1968 |           summary: `Team Meeting ${timestamp} Discussion` // Contains "Team Meeting"
1969 |         };
1970 |         
1971 |         const similarResult = await client.callTool({
1972 |           name: 'create-event',
1973 |           arguments: {
1974 |             calendarId: TEST_CALENDAR_ID,
1975 |             ...similarTitleEvent,
1976 |             allowDuplicates: true // Allow creation despite warning
1977 |           }
1978 |         });
1979 |         
1980 |         const similarResponse = JSON.parse((similarResult.content as any)[0].text);
1981 |         expect(similarResponse.event).toBeDefined();
1982 |         expect(similarResponse.warnings).toBeDefined();
1983 |         expect(similarResponse.duplicates).toBeDefined();
1984 |         expect(similarResponse.duplicates.length).toBeGreaterThan(0);
1985 |         if (similarResponse.duplicates[0]) {
1986 |           expect(similarResponse.duplicates[0].event.similarity).toBeGreaterThanOrEqual(0.7);
1987 |         }
1988 |         const similarEventId = extractEventId(similarResult);
1989 |         if (similarEventId) createdEventIds.push(similarEventId);
1990 |         
1991 |         // Test 3: Same title on same day but different time = NO DUPLICATE (different time window)
1992 |         const laterTime = new Date(baseEvent.start);
1993 |         laterTime.setHours(laterTime.getHours() + 3);
1994 |         const laterEndTime = new Date(baseEvent.end);
1995 |         laterEndTime.setHours(laterEndTime.getHours() + 3);
1996 |         
1997 |         const sameDayEvent = {
1998 |           ...baseEvent,
1999 |           start: TestDataFactory.formatDateTimeRFC3339(laterTime),
2000 |           end: TestDataFactory.formatDateTimeRFC3339(laterEndTime)
2001 |         };
2002 |         
2003 |         const sameDayResult = await client.callTool({
2004 |           name: 'create-event',
2005 |           arguments: {
2006 |             calendarId: TEST_CALENDAR_ID,
2007 |             ...sameDayEvent
2008 |           }
2009 |         });
2010 |         
2011 |         // With exact time window search, events at different times are NOT detected as duplicates
2012 |         const sameDayResponse = JSON.parse((sameDayResult.content as any)[0].text);
2013 |         expect(sameDayResponse.event).toBeDefined();
2014 |         expect(sameDayResponse.duplicates).toBeUndefined();
2015 |         expect(sameDayResponse.warnings).toBeUndefined();
2016 |         const sameDayEventId = extractEventId(sameDayResult);
2017 |         if (sameDayEventId) createdEventIds.push(sameDayEventId);
2018 |         
2019 |         // Test 4: Same title but different day = NO DUPLICATE (different time window)
2020 |         const nextWeek = new Date(baseEvent.start);
2021 |         nextWeek.setDate(nextWeek.getDate() + 7);
2022 |         const nextWeekEnd = new Date(baseEvent.end);
2023 |         nextWeekEnd.setDate(nextWeekEnd.getDate() + 7);
2024 |         
2025 |         const differentDayEvent = {
2026 |           ...baseEvent,
2027 |           start: TestDataFactory.formatDateTimeRFC3339(nextWeek),
2028 |           end: TestDataFactory.formatDateTimeRFC3339(nextWeekEnd)
2029 |         };
2030 |         
2031 |         const differentDayResult = await client.callTool({
2032 |           name: 'create-event',
2033 |           arguments: {
2034 |             calendarId: TEST_CALENDAR_ID,
2035 |             ...differentDayEvent
2036 |           }
2037 |         });
2038 |         
2039 |         // With exact time window search, events on different days are NOT detected as duplicates
2040 |         const differentDayResponse = JSON.parse((differentDayResult.content as any)[0].text);
2041 |         expect(differentDayResponse.event).toBeDefined();
2042 |         expect(differentDayResponse.duplicates).toBeUndefined();
2043 |         const differentDayEventId = extractEventId(differentDayResult);
2044 |         if (differentDayEventId) createdEventIds.push(differentDayEventId);
2045 |       });
2046 |       
2047 |     });
2048 |     
2049 |     describe('Adjacent Event Handling (No False Positives)', () => {
2050 |       it('should not flag back-to-back meetings as conflicts', async () => {
2051 |         const baseDate = new Date();
2052 |         baseDate.setDate(baseDate.getDate() + 7); // 7 days from now
2053 |         baseDate.setHours(9, 0, 0, 0);
2054 |         
2055 |         // Pre-check: Clear any existing events in this time window
2056 |         const timeRangeStart = new Date(baseDate);
2057 |         timeRangeStart.setHours(0, 0, 0, 0); // Start of day
2058 |         const timeRangeEnd = new Date(baseDate);
2059 |         timeRangeEnd.setHours(23, 59, 59, 999); // End of day
2060 |         
2061 |         const existingEventsResult = await client.callTool({
2062 |           name: 'list-events',
2063 |           arguments: {
2064 |             calendarId: TEST_CALENDAR_ID,
2065 |             timeMin: TestDataFactory.formatDateTimeRFC3339(timeRangeStart),
2066 |             timeMax: TestDataFactory.formatDateTimeRFC3339(timeRangeEnd)
2067 |           }
2068 |         });
2069 |         
2070 |         // Delete any existing events found
2071 |         const existingEventIds = TestDataFactory.extractAllEventIds(existingEventsResult);
2072 |         if (existingEventIds.length > 0) {
2073 |           console.log(`🧹 Pre-test cleanup: Removing ${existingEventIds.length} existing events from test time window`);
2074 |           for (const eventId of existingEventIds) {
2075 |             try {
2076 |               await client.callTool({
2077 |                 name: 'delete-event',
2078 |                 arguments: {
2079 |                   calendarId: TEST_CALENDAR_ID,
2080 |                   eventId,
2081 |                   sendUpdates: SEND_UPDATES
2082 |                 }
2083 |               });
2084 |             } catch (error) {
2085 |               // Ignore errors - event might be protected or already deleted
2086 |             }
2087 |           }
2088 |           // Wait for deletions to propagate
2089 |           await new Promise(resolve => setTimeout(resolve, 2000));
2090 |         }
2091 |         
2092 |         // Create first meeting 9-10am
2093 |         const timestamp = Date.now();
2094 |         const firstStart = new Date(baseDate);
2095 |         const firstEnd = new Date(firstStart);
2096 |         firstEnd.setHours(10, 0, 0, 0);
2097 |         
2098 |         const firstMeeting = TestDataFactory.createSingleEvent({
2099 |           summary: `Morning Standup ${timestamp}`,
2100 |           description: 'Daily team sync',
2101 |           location: 'Room A',
2102 |           start: TestDataFactory.formatDateTimeRFC3339(firstStart),
2103 |           end: TestDataFactory.formatDateTimeRFC3339(firstEnd)
2104 |         });
2105 |         
2106 |         const firstId = await createTestEvent(firstMeeting);
2107 |         createdEventIds.push(firstId);
2108 |         
2109 |         // Note: Google Calendar has eventual consistency - events may not immediately
2110 |         // appear in list queries. This delay helps but doesn't guarantee visibility.
2111 |         await new Promise(resolve => setTimeout(resolve, 3000));
2112 |         
2113 |         // Create second meeting 10-11am (immediately after)
2114 |         const secondStart = new Date(baseDate);
2115 |         secondStart.setHours(10, 0, 0, 0);
2116 |         const secondEnd = new Date(secondStart);
2117 |         secondEnd.setHours(11, 0, 0, 0);
2118 |         
2119 |         const secondMeeting = TestDataFactory.createSingleEvent({
2120 |           summary: `Project Review ${timestamp}`,
2121 |           description: 'Weekly project status update',
2122 |           location: 'Room B',
2123 |           start: TestDataFactory.formatDateTimeRFC3339(secondStart),
2124 |           end: TestDataFactory.formatDateTimeRFC3339(secondEnd)
2125 |         });
2126 |         
2127 |         const result = await client.callTool({
2128 |           name: 'create-event',
2129 |           arguments: {
2130 |             calendarId: TEST_CALENDAR_ID,
2131 |             ...secondMeeting
2132 |           }
2133 |         });
2134 |         
2135 |         // Should not show conflict warning for adjacent events
2136 |         const resultResponse = JSON.parse((result.content as any)[0].text);
2137 |         expect(resultResponse.event).toBeDefined();
2138 |         expect(resultResponse.conflicts).toBeUndefined();
2139 |         expect(resultResponse.warnings).toBeUndefined();
2140 |         const secondId = extractEventId(result);
2141 |         if (secondId) createdEventIds.push(secondId);
2142 |         
2143 |         // Create third meeting 10:30-11:30am (overlaps with second)
2144 |         const thirdStart = new Date(baseDate);
2145 |         thirdStart.setHours(10, 30, 0, 0); // 10:30 AM
2146 |         const thirdEnd = new Date(thirdStart);
2147 |         thirdEnd.setHours(11, 30, 0, 0); // 11:30 AM
2148 |         
2149 |         const thirdMeeting = TestDataFactory.createSingleEvent({
2150 |           summary: 'Design Discussion',
2151 |           description: 'UI/UX design review',
2152 |           location: 'Design Lab',
2153 |           start: TestDataFactory.formatDateTimeRFC3339(thirdStart),
2154 |           end: TestDataFactory.formatDateTimeRFC3339(thirdEnd)
2155 |         });
2156 |         
2157 |         const conflictResult = await client.callTool({
2158 |           name: 'create-event',
2159 |           arguments: {
2160 |             calendarId: TEST_CALENDAR_ID,
2161 |             ...thirdMeeting
2162 |           }
2163 |         });
2164 |         
2165 |         // Should show conflict for actual overlap
2166 |         const conflictResponse = JSON.parse((conflictResult.content as any)[0].text);
2167 |         expect(conflictResponse.event).toBeDefined();
2168 |         expect(conflictResponse.warnings).toBeDefined();
2169 |         expect(conflictResponse.conflicts).toBeDefined();
2170 |         expect(conflictResponse.conflicts.length).toBeGreaterThan(0);
2171 |         if (conflictResponse.conflicts[0]) {
2172 |           expect(conflictResponse.conflicts[0].overlap?.duration).toContain('30 minute');
2173 |           expect(conflictResponse.conflicts[0].overlap?.percentage).toContain('50%');
2174 |         }
2175 |         const thirdId = extractEventId(conflictResult);
2176 |         if (thirdId) createdEventIds.push(thirdId);
2177 |       });
2178 |     });
2179 |     
2180 |     describe('Unified Threshold Configuration', () => {
2181 |       it('should use configurable duplicate detection threshold', async () => {
2182 |         // Use fixed time for consistent testing
2183 |         const fixedStart = new Date();
2184 |         fixedStart.setDate(fixedStart.getDate() + 8); // 8 days from now
2185 |         fixedStart.setHours(10, 0, 0, 0); // 10 AM
2186 |         const fixedEnd = new Date(fixedStart);
2187 |         fixedEnd.setHours(11, 0, 0, 0); // 11 AM
2188 |         
2189 |         // Pre-check: Clear any existing events in this time window
2190 |         const timeRangeStart = new Date(fixedStart);
2191 |         timeRangeStart.setHours(0, 0, 0, 0); // Start of day
2192 |         const timeRangeEnd = new Date(fixedStart);
2193 |         timeRangeEnd.setHours(23, 59, 59, 999); // End of day
2194 |         
2195 |         const existingEventsResult = await client.callTool({
2196 |           name: 'list-events',
2197 |           arguments: {
2198 |             calendarId: TEST_CALENDAR_ID,
2199 |             timeMin: TestDataFactory.formatDateTimeRFC3339(timeRangeStart),
2200 |             timeMax: TestDataFactory.formatDateTimeRFC3339(timeRangeEnd)
2201 |           }
2202 |         });
2203 |         
2204 |         // Delete any existing events found
2205 |         const existingEventIds = TestDataFactory.extractAllEventIds(existingEventsResult);
2206 |         if (existingEventIds.length > 0) {
2207 |           console.log(`🧹 Pre-test cleanup: Removing ${existingEventIds.length} existing events from test time window`);
2208 |           for (const eventId of existingEventIds) {
2209 |             try {
2210 |               await client.callTool({
2211 |                 name: 'delete-event',
2212 |                 arguments: {
2213 |                   calendarId: TEST_CALENDAR_ID,
2214 |                   eventId,
2215 |                   sendUpdates: SEND_UPDATES
2216 |                 }
2217 |               });
2218 |             } catch (error) {
2219 |               // Ignore errors - event might be protected or already deleted
2220 |             }
2221 |           }
2222 |           // Wait for deletions to propagate
2223 |           await new Promise(resolve => setTimeout(resolve, 2000));
2224 |         }
2225 |         
2226 |         const timestamp = Date.now();
2227 |         const baseEvent = TestDataFactory.createSingleEvent({
2228 |           summary: `Quarterly Planning ${timestamp}`,
2229 |           start: TestDataFactory.formatDateTimeRFC3339(fixedStart),
2230 |           end: TestDataFactory.formatDateTimeRFC3339(fixedEnd)
2231 |         });
2232 |         
2233 |         const baseId = await createTestEvent(baseEvent);
2234 |         createdEventIds.push(baseId);
2235 |         
2236 |         // Note: Google Calendar has eventual consistency - events may not immediately
2237 |         // appear in list queries. This delay helps but doesn't guarantee visibility.
2238 |         await new Promise(resolve => setTimeout(resolve, 3000));
2239 |         
2240 |         // Test with custom threshold of 0.5 for similar title at same time
2241 |         const similarEvent = {
2242 |           ...baseEvent,
2243 |           summary: `Quarterly Planning ${timestamp} Meeting`  // Similar but not identical title
2244 |         };
2245 |         
2246 |         const lowThresholdResult = await client.callTool({
2247 |           name: 'create-event',
2248 |           arguments: {
2249 |             calendarId: TEST_CALENDAR_ID,
2250 |             ...similarEvent,
2251 |             duplicateSimilarityThreshold: 0.5,
2252 |             allowDuplicates: true // Allow creation despite warning
2253 |           }
2254 |         });
2255 |         
2256 |         // Track for cleanup immediately after creation
2257 |         const lowThresholdId = extractEventId(lowThresholdResult);
2258 |         if (lowThresholdId) createdEventIds.push(lowThresholdId);
2259 |         
2260 |         // Should show warning since similarity > 50% threshold
2261 |         const lowThresholdResponse = JSON.parse((lowThresholdResult.content as any)[0].text);
2262 |         expect(lowThresholdResponse.event).toBeDefined();
2263 |         expect(lowThresholdResponse.duplicates).toBeDefined();
2264 |         expect(lowThresholdResponse.duplicates.length).toBeGreaterThan(0);
2265 |         
2266 |         // Test with high threshold of 0.9 (should not flag ~70% similarity)
2267 |         const slightlyDifferentEvent = {
2268 |           ...baseEvent,
2269 |           summary: 'Q4 Planning'  // Different enough title to be below 90% threshold
2270 |         };
2271 |         
2272 |         const highThresholdResult = await client.callTool({
2273 |           name: 'create-event',
2274 |           arguments: {
2275 |             calendarId: TEST_CALENDAR_ID,
2276 |             ...slightlyDifferentEvent,
2277 |             duplicateSimilarityThreshold: 0.9
2278 |           }
2279 |         });
2280 |         
2281 |         // Track for cleanup immediately after creation
2282 |         const highThresholdId = extractEventId(highThresholdResult);
2283 |         if (highThresholdId) createdEventIds.push(highThresholdId);
2284 |         
2285 |         // Should not show DUPLICATE warning since similarity < 90% threshold
2286 |         // Note: May show conflict warning if events overlap in time
2287 |         const highThresholdResponse = JSON.parse((highThresholdResult.content as any)[0].text);
2288 |         expect(highThresholdResponse.event).toBeDefined();
2289 |         expect(highThresholdResponse.duplicates).toBeUndefined();
2290 |       });
2291 |       
2292 |       it('should allow exact duplicates with allowDuplicates flag', async () => {
2293 |         // Use fixed time for exact duplicate
2294 |         const fixedStart = new Date();
2295 |         fixedStart.setDate(fixedStart.getDate() + 9); // 9 days from now
2296 |         fixedStart.setHours(15, 0, 0, 0); // 3 PM
2297 |         const fixedEnd = new Date(fixedStart);
2298 |         fixedEnd.setHours(16, 0, 0, 0); // 4 PM
2299 |         
2300 |         // Pre-check: Clear any existing events in this time window
2301 |         const timeRangeStart = new Date(fixedStart);
2302 |         timeRangeStart.setHours(0, 0, 0, 0); // Start of day
2303 |         const timeRangeEnd = new Date(fixedStart);
2304 |         timeRangeEnd.setHours(23, 59, 59, 999); // End of day
2305 |         
2306 |         const existingEventsResult = await client.callTool({
2307 |           name: 'list-events',
2308 |           arguments: {
2309 |             calendarId: TEST_CALENDAR_ID,
2310 |             timeMin: TestDataFactory.formatDateTimeRFC3339(timeRangeStart),
2311 |             timeMax: TestDataFactory.formatDateTimeRFC3339(timeRangeEnd)
2312 |           }
2313 |         });
2314 |         
2315 |         // Delete any existing events found
2316 |         const existingEventIds = TestDataFactory.extractAllEventIds(existingEventsResult);
2317 |         if (existingEventIds.length > 0) {
2318 |           console.log(`🧹 Pre-test cleanup: Removing ${existingEventIds.length} existing events from test time window`);
2319 |           for (const eventId of existingEventIds) {
2320 |             try {
2321 |               await client.callTool({
2322 |                 name: 'delete-event',
2323 |                 arguments: {
2324 |                   calendarId: TEST_CALENDAR_ID,
2325 |                   eventId,
2326 |                   sendUpdates: SEND_UPDATES
2327 |                 }
2328 |               });
2329 |             } catch (error) {
2330 |               // Ignore errors - event might be protected or already deleted
2331 |             }
2332 |           }
2333 |           // Wait for deletions to propagate
2334 |           await new Promise(resolve => setTimeout(resolve, 2000));
2335 |         }
2336 |         
2337 |         const event = TestDataFactory.createSingleEvent({
2338 |           summary: `Important Presentation ${Date.now()}`,
2339 |           start: TestDataFactory.formatDateTimeRFC3339(fixedStart),
2340 |           end: TestDataFactory.formatDateTimeRFC3339(fixedEnd)
2341 |         });
2342 |         
2343 |         const firstId = await createTestEvent(event);
2344 |         createdEventIds.push(firstId);
2345 |         
2346 |         // Note: Google Calendar has eventual consistency - events may not immediately
2347 |         // appear in list queries. This delay helps but doesn't guarantee visibility.
2348 |         await new Promise(resolve => setTimeout(resolve, 3000));
2349 |         
2350 |         // Try to create exact duplicate with allowDuplicates=true
2351 |         const duplicateResult = await client.callTool({
2352 |           name: 'create-event',
2353 |           arguments: {
2354 |             calendarId: TEST_CALENDAR_ID,
2355 |             ...event,
2356 |             allowDuplicates: true
2357 |           }
2358 |         });
2359 |         
2360 |         // Should create with warning but not block
2361 |         const duplicateResponse = JSON.parse((duplicateResult.content as any)[0].text);
2362 |         expect(duplicateResponse.event).toBeDefined();
2363 |         expect(duplicateResponse.warnings).toBeDefined();
2364 |         expect(duplicateResponse.duplicates).toBeDefined();
2365 |         expect(duplicateResponse.duplicates.length).toBeGreaterThan(0);
2366 |         expect(duplicateResponse.duplicates[0].event.similarity).toBeGreaterThan(0.6); // Similarity may vary due to timestamps
2367 |         const duplicateId = extractEventId(duplicateResult);
2368 |         if (duplicateId) createdEventIds.push(duplicateId);
2369 |       });
2370 |     });
2371 |     
2372 |     describe('Conflict Detection Performance', () => {
2373 |       it('should detect conflicts for overlapping events', async () => {
2374 |         // Create multiple events for conflict checking
2375 |         const baseTime = new Date();
2376 |         baseTime.setDate(baseTime.getDate() + 10); // 10 days from now
2377 |         baseTime.setHours(14, 0, 0, 0); // 2 PM
2378 |         
2379 |         // Pre-check: Clear any existing events in this time window
2380 |         const timeRangeStart = new Date(baseTime);
2381 |         timeRangeStart.setHours(0, 0, 0, 0); // Start of day
2382 |         const timeRangeEnd = new Date(baseTime);
2383 |         timeRangeEnd.setHours(23, 59, 59, 999); // End of day
2384 |         
2385 |         const existingEventsResult = await client.callTool({
2386 |           name: 'list-events',
2387 |           arguments: {
2388 |             calendarId: TEST_CALENDAR_ID,
2389 |             timeMin: TestDataFactory.formatDateTimeRFC3339(timeRangeStart),
2390 |             timeMax: TestDataFactory.formatDateTimeRFC3339(timeRangeEnd)
2391 |           }
2392 |         });
2393 |         
2394 |         // Delete any existing events found
2395 |         const existingEventIds = TestDataFactory.extractAllEventIds(existingEventsResult);
2396 |         if (existingEventIds.length > 0) {
2397 |           console.log(`🧹 Pre-test cleanup: Removing ${existingEventIds.length} existing events from test time window`);
2398 |           for (const eventId of existingEventIds) {
2399 |             try {
2400 |               await client.callTool({
2401 |                 name: 'delete-event',
2402 |                 arguments: {
2403 |                   calendarId: TEST_CALENDAR_ID,
2404 |                   eventId,
2405 |                   sendUpdates: SEND_UPDATES
2406 |                 }
2407 |               });
2408 |             } catch (error) {
2409 |               // Ignore errors - event might be protected or already deleted
2410 |             }
2411 |           }
2412 |           // Wait for deletions to propagate
2413 |           await new Promise(resolve => setTimeout(resolve, 2000));
2414 |         }
2415 |         
2416 |         const events = [];
2417 |         for (let i = 0; i < 3; i++) {
2418 |           const startTime = new Date(baseTime.getTime() + i * 2 * 60 * 60 * 1000);
2419 |           const event = TestDataFactory.createSingleEvent({
2420 |             summary: `Cache Test Event ${i + 1} ${Date.now()}`,
2421 |             start: TestDataFactory.formatDateTimeRFC3339(startTime),
2422 |             end: TestDataFactory.formatDateTimeRFC3339(new Date(startTime.getTime() + 60 * 60 * 1000))
2423 |           });
2424 |           const id = await createTestEvent(event);
2425 |           createdEventIds.push(id);
2426 |           events.push(event);
2427 |         }
2428 |         
2429 |         // Longer delay to ensure events are indexed in Google Calendar
2430 |         await new Promise(resolve => setTimeout(resolve, 3000));
2431 |         
2432 |         // First conflict check
2433 |         const overlappingEvent = TestDataFactory.createSingleEvent({
2434 |           summary: 'Overlapping Meeting',
2435 |           start: events[1].start, // Same time as second event
2436 |           end: events[1].end
2437 |         });
2438 |         
2439 |         const result1 = await client.callTool({
2440 |           name: 'create-event',
2441 |           arguments: {
2442 |             calendarId: TEST_CALENDAR_ID,
2443 |             ...overlappingEvent,
2444 |             allowDuplicates: true
2445 |           }
2446 |         });
2447 |         
2448 |         // Should detect a conflict (100% overlap)
2449 |         const responseText = (result1.content as any)[0].text;
2450 |         const response1 = JSON.parse(responseText);
2451 |         expect(response1.conflicts).toBeDefined();
2452 |         expect(response1.conflicts.length).toBeGreaterThan(0);
2453 |         expect(response1.conflicts[0].overlap.percentage).toBe('100%');
2454 |         const overlappingId = response1.event?.id;
2455 |         if (overlappingId) createdEventIds.push(overlappingId);
2456 |         
2457 |         // Second conflict check with different event
2458 |         const anotherOverlapping = TestDataFactory.createSingleEvent({
2459 |           summary: 'Another Overlapping Meeting',
2460 |           start: events[1].start,
2461 |           end: events[1].end
2462 |         });
2463 |         
2464 |         const result2 = await client.callTool({
2465 |           name: 'create-event',
2466 |           arguments: {
2467 |             calendarId: TEST_CALENDAR_ID,
2468 |             ...anotherOverlapping,
2469 |             allowDuplicates: true
2470 |           }
2471 |         });
2472 |         
2473 |         // Should also detect a conflict
2474 |         const responseText2 = (result2.content as any)[0].text;
2475 |         const response2 = JSON.parse(responseText2);
2476 |         expect(response2.conflicts).toBeDefined();
2477 |         expect(response2.conflicts.length).toBeGreaterThan(0);
2478 |         // Check that at least one conflict has 100% overlap
2479 |         const has100PercentOverlap = response2.conflicts.some((c: any) => 
2480 |           c.overlap && c.overlap.percentage === '100%'
2481 |         );
2482 |         expect(has100PercentOverlap).toBe(true);
2483 |         const anotherId = extractEventId(result2);
2484 |         if (anotherId) createdEventIds.push(anotherId);
2485 |       });
2486 |     });
2487 |   });
2488 | 
2489 |   describe('Performance Benchmarks', () => {
2490 |     it('should complete basic operations within reasonable time limits', async () => {
2491 |       // Create a test event for performance testing
2492 |       const eventData = TestDataFactory.createSingleEvent({
2493 |         summary: `Performance Test Event ${Date.now()}`
2494 |       });
2495 |       
2496 |       const eventId = await createTestEvent(eventData);
2497 |       createdEventIds.push(eventId);
2498 |       
2499 |       // Test various operations and collect metrics
2500 |       const timeRanges = TestDataFactory.getTimeRanges();
2501 |       
2502 |       await verifyEventInList(eventId, timeRanges.nextWeek);
2503 |       await verifyEventInSearch(eventData.summary);
2504 |       
2505 |       // Get all performance metrics
2506 |       const metrics = testFactory.getPerformanceMetrics();
2507 |       
2508 |       // Log performance results
2509 |       console.log('\n📊 Performance Metrics:');
2510 |       metrics.forEach(metric => {
2511 |         console.log(`  ${metric.operation}: ${metric.duration}ms (${metric.success ? '✅' : '❌'})`);
2512 |       });
2513 |       
2514 |       // Basic performance assertions
2515 |       const createMetric = metrics.find(m => m.operation === 'create-event');
2516 |       const listMetric = metrics.find(m => m.operation === 'list-events');
2517 |       const searchMetric = metrics.find(m => m.operation === 'search-events');
2518 |       
2519 |       expect(createMetric?.success).toBe(true);
2520 |       expect(listMetric?.success).toBe(true);
2521 |       expect(searchMetric?.success).toBe(true);
2522 |       
2523 |       // All operations should complete within 30 seconds
2524 |       metrics.forEach(metric => {
2525 |         expect(metric.duration).toBeLessThan(30000);
2526 |       });
2527 |     });
2528 |   });
2529 | 
2530 |   // Helper Functions
2531 |   function extractEventId(result: any): string | null {
2532 |     try {
2533 |       const text = (result.content as any)[0]?.text;
2534 |       if (!text) return null;
2535 |       
2536 |       const response = JSON.parse(text);
2537 |       return response.event?.id || null;
2538 |     } catch {
2539 |       return null;
2540 |     }
2541 |   }
2542 | 
2543 |   async function createTestEvent(eventData: TestEvent, allowDuplicates: boolean = true): Promise<string> {
2544 |     const startTime = testFactory.startTimer('create-event');
2545 | 
2546 |     try {
2547 |       const result = await client.callTool({
2548 |         name: 'create-event',
2549 |         arguments: {
2550 |           calendarId: TEST_CALENDAR_ID,
2551 |           ...eventData,
2552 |           allowDuplicates
2553 |         }
2554 |       });
2555 |       
2556 |       testFactory.endTimer('create-event', startTime, true);
2557 |       
2558 |       expect(TestDataFactory.validateEventResponse(result)).toBe(true);
2559 |       
2560 |       // Handle structured JSON response
2561 |       const text = (result.content as any)[0]?.text;
2562 |       if (!text) throw new Error('No response text');
2563 |       
2564 |       // Check if it's an error message (not JSON)
2565 |       if (text.includes('Duplicate event detected') || text.includes('Error:')) {
2566 |         throw new Error(text);
2567 |       }
2568 |       
2569 |       const response = JSON.parse(text);
2570 |       const eventId = response.event?.id;
2571 |       
2572 |       expect(eventId).toBeTruthy();
2573 |       
2574 |       testFactory.addCreatedEventId(eventId);
2575 |       
2576 |       return eventId;
2577 |     } catch (error) {
2578 |       testFactory.endTimer('create-event', startTime, false, String(error));
2579 |       throw error;
2580 |     }
2581 |   }
2582 | 
2583 |   async function verifyEventInList(eventId: string, timeRange: { timeMin: string; timeMax: string }): Promise<void> {
2584 |     const startTime = testFactory.startTimer('list-events');
2585 |     
2586 |     try {
2587 |       const result = await client.callTool({
2588 |         name: 'list-events',
2589 |         arguments: {
2590 |           calendarId: TEST_CALENDAR_ID,
2591 |           timeMin: timeRange.timeMin,
2592 |           timeMax: timeRange.timeMax
2593 |         }
2594 |       });
2595 |       
2596 |       testFactory.endTimer('list-events', startTime, true);
2597 |       
2598 |       expect(TestDataFactory.validateEventResponse(result)).toBe(true);
2599 |       
2600 |       // Handle structured JSON response
2601 |       const text = (result.content as any)[0]?.text;
2602 |       const response = JSON.parse(text);
2603 |       
2604 |       // Check if the event ID is in the list
2605 |       const eventIds = response.events?.map((e: any) => e.id) || [];
2606 |       expect(eventIds).toContain(eventId);
2607 |     } catch (error) {
2608 |       testFactory.endTimer('list-events', startTime, false, String(error));
2609 |       throw error;
2610 |     }
2611 |   }
2612 | 
2613 |   async function verifyEventInSearch(query: string): Promise<void> {
2614 |     // Add small delay to allow Google Calendar search index to update
2615 |     await new Promise(resolve => setTimeout(resolve, 1000));
2616 |     
2617 |     const startTime = testFactory.startTimer('search-events');
2618 |     
2619 |     try {
2620 |       const timeRanges = TestDataFactory.getTimeRanges();
2621 |       const result = await client.callTool({
2622 |         name: 'search-events',
2623 |         arguments: {
2624 |           calendarId: TEST_CALENDAR_ID,
2625 |           query,
2626 |           timeMin: timeRanges.nextWeek.timeMin,
2627 |           timeMax: timeRanges.nextWeek.timeMax
2628 |         }
2629 |       });
2630 |       
2631 |       testFactory.endTimer('search-events', startTime, true);
2632 |       
2633 |       expect(TestDataFactory.validateEventResponse(result)).toBe(true);
2634 |       
2635 |       // Handle structured JSON response
2636 |       const text = (result.content as any)[0]?.text;
2637 |       const response = JSON.parse(text);
2638 |       
2639 |       // Check if query matches any event in results
2640 |       const hasMatch = response.events?.some((e: any) => 
2641 |         e.summary?.toLowerCase().includes(query.toLowerCase()) ||
2642 |         e.description?.toLowerCase().includes(query.toLowerCase())
2643 |       );
2644 |       expect(hasMatch).toBe(true);
2645 |     } catch (error) {
2646 |       testFactory.endTimer('search-events', startTime, false, String(error));
2647 |       throw error;
2648 |     }
2649 |   }
2650 | 
2651 |   async function updateTestEvent(eventId: string, updates: Partial<TestEvent>): Promise<void> {
2652 |     const startTime = testFactory.startTimer('update-event');
2653 |     
2654 |     try {
2655 |       const result = await client.callTool({
2656 |         name: 'update-event',
2657 |         arguments: {
2658 |           calendarId: TEST_CALENDAR_ID,
2659 |           eventId,
2660 |           ...updates,
2661 |           timeZone: updates.timeZone || 'America/Los_Angeles',
2662 |           sendUpdates: SEND_UPDATES
2663 |         }
2664 |       });
2665 |       
2666 |       testFactory.endTimer('update-event', startTime, true);
2667 |       
2668 |       expect(TestDataFactory.validateEventResponse(result)).toBe(true);
2669 |     } catch (error) {
2670 |       testFactory.endTimer('update-event', startTime, false, String(error));
2671 |       throw error;
2672 |     }
2673 |   }
2674 | 
2675 |   async function testRecurringEventUpdates(eventId: string): Promise<void> {
2676 |     // Test updating all instances
2677 |     await updateTestEvent(eventId, {
2678 |       summary: 'Updated Recurring Meeting - All Instances'
2679 |     });
2680 |     
2681 |     // Verify the update
2682 |     await verifyEventInSearch('Recurring');
2683 |   }
2684 | 
2685 |   async function cleanupTestEvents(eventIds: string[]): Promise<void> {
2686 |     const cleanupResults = { success: 0, failed: 0 };
2687 |     
2688 |     for (const eventId of eventIds) {
2689 |       try {
2690 |         const deleteStartTime = testFactory.startTimer('delete-event');
2691 |         
2692 |         await client.callTool({
2693 |           name: 'delete-event',
2694 |           arguments: {
2695 |             calendarId: TEST_CALENDAR_ID,
2696 |             eventId,
2697 |             sendUpdates: SEND_UPDATES
2698 |           }
2699 |         });
2700 |         
2701 |         testFactory.endTimer('delete-event', deleteStartTime, true);
2702 |         cleanupResults.success++;
2703 |       } catch (error: any) {
2704 |         const deleteStartTime = testFactory.startTimer('delete-event');
2705 |         testFactory.endTimer('delete-event', deleteStartTime, false, String(error));
2706 |         
2707 |         // Only warn for non-404 errors (404 means event was already deleted)
2708 |         const errorMessage = String(error);
2709 |         if (!errorMessage.includes('404') && !errorMessage.includes('Not Found')) {
2710 |           console.warn(`⚠️  Failed to cleanup event ${eventId}:`, errorMessage);
2711 |         }
2712 |         cleanupResults.failed++;
2713 |       }
2714 |     }
2715 |     
2716 |     if (cleanupResults.success > 0) {
2717 |       console.log(`✅ Successfully deleted ${cleanupResults.success} test event(s)`);
2718 |     }
2719 |     if (cleanupResults.failed > 0 && cleanupResults.failed !== eventIds.length) {
2720 |       console.log(`⚠️  Failed to delete ${cleanupResults.failed} test event(s) (may have been already deleted)`);
2721 |     }
2722 |   }
2723 | 
2724 |   async function cleanupAllTestEvents(): Promise<void> {
2725 |     const allEventIds = testFactory.getCreatedEventIds();
2726 |     await cleanupTestEvents(allEventIds);
2727 |     testFactory.clearCreatedEventIds();
2728 |   }
2729 | 
2730 |   function logPerformanceSummary(): void {
2731 |     const metrics = testFactory.getPerformanceMetrics();
2732 |     if (metrics.length === 0) return;
2733 |     
2734 |     console.log('\n📈 Final Performance Summary:');
2735 |     
2736 |     const byOperation = metrics.reduce((acc, metric) => {
2737 |       if (!acc[metric.operation]) {
2738 |         acc[metric.operation] = {
2739 |           count: 0,
2740 |           totalDuration: 0,
2741 |           successCount: 0,
2742 |           errors: []
2743 |         };
2744 |       }
2745 |       
2746 |       acc[metric.operation].count++;
2747 |       acc[metric.operation].totalDuration += metric.duration;
2748 |       if (metric.success) {
2749 |         acc[metric.operation].successCount++;
2750 |       } else if (metric.error) {
2751 |         acc[metric.operation].errors.push(metric.error);
2752 |       }
2753 |       
2754 |       return acc;
2755 |     }, {} as Record<string, { count: number; totalDuration: number; successCount: number; errors: string[] }>);
2756 |     
2757 |     Object.entries(byOperation).forEach(([operation, stats]) => {
2758 |       const avgDuration = Math.round(stats.totalDuration / stats.count);
2759 |       const successRate = Math.round((stats.successCount / stats.count) * 100);
2760 |       
2761 |       console.log(`  ${operation}:`);
2762 |       console.log(`    Calls: ${stats.count}`);
2763 |       console.log(`    Avg Duration: ${avgDuration}ms`);
2764 |       console.log(`    Success Rate: ${successRate}%`);
2765 |       
2766 |       if (stats.errors.length > 0) {
2767 |         console.log(`    Errors: ${stats.errors.length}`);
2768 |       }
2769 |     });
2770 |   }
2771 | });
2772 | 
```
Page 6/6FirstPrevNextLast