Skip to content

Conversation

zomars
Copy link
Contributor

@zomars zomars commented Jul 15, 2025

fix: implement proper cache merging for Google Calendar sync tokens

Summary

This PR fixes a critical bug where Google Calendar incremental sync was completely replacing cached events instead of merging them with existing data. This caused availability gaps where users appeared free when they were actually busy.

Root Cause: The upsertCachedAvailability() function was performing complete cache replacement instead of merging incremental changes from Google's sync tokens with existing cached events.

Solution:

  • Added mergeBusyTimesWithCache() method in CalendarService.ts to merge incremental events with existing cache
  • Added mergeCacheValues() function in calendar-cache.repository.ts to handle cache merging at the repository level
  • Modified setAvailabilityInCacheWithSyncToken() to retrieve existing cache before merging
  • Added comprehensive test suite with 4 critical test cases that previously failed and now pass

Impact: Prevents booking conflicts and availability gaps caused by lost calendar events during incremental sync.

Review & Testing Checklist for Human

  • Manual testing with real Google Calendar data - Test adding/updating/deleting events via Google Calendar web interface and verify cache reflects changes correctly (most critical)
  • Concurrent webhook testing - Simulate multiple rapid webhook calls to ensure cache merging handles race conditions
  • Edge case validation - Test with malformed cache data, empty events, large event sets (500+ events)
  • Performance testing - Measure cache operation performance with large datasets to ensure no significant slowdown
  • Regression testing - Verify existing Google Calendar functionality (initial sync, full sync, webhook processing) still works correctly

Recommended Test Plan:

  1. Set up real Google Calendar integration in staging
  2. Create calendar events, verify they appear in cache
  3. Add new event via Google Calendar web → trigger webhook → verify cache contains both old and new events
  4. Update existing event → verify cache reflects update without losing other events
  5. Delete event → verify only that event is removed from cache

Diagram

%%{ init : { "theme" : "default" }}%%
graph TB
    Webhook["api/webhook.ts"]
    CalendarService["CalendarService.ts"]
    Repository["calendar-cache.repository.ts"]
    Cache["CalendarCache (Database)"]
    
    Webhook --> CalendarService
    CalendarService --> Repository
    Repository --> Cache
    
    CalendarService --> MergeBusyTimes["mergeBusyTimesWithCache()"]:::major-edit
    Repository --> MergeCacheValues["mergeCacheValues()"]:::major-edit
    Repository --> UpsertCache["upsertCachedAvailability()"]:::minor-edit
    
    TestFile["google-calendar-sync-tokens.test.ts"]:::major-edit
    TestFile --> CalendarService
    
    subgraph Legend
        L1["Major Edit"]:::major-edit
        L2["Minor Edit"]:::minor-edit
        L3["Context/No Edit"]:::context
    end
    
    classDef major-edit fill:#90EE90
    classDef minor-edit fill:#87CEEB
    classDef context fill:#FFFFFF
Loading

Notes

  • Session: Requested by @zomars, implemented in Devin session: https://app.devin.ai/sessions/155cfb747d684dd6a5f6175205bc4573
  • Test Results: All 4 cache merging tests now pass (previously all failed, proving the bug existed)
  • Type Safety: Fixed TypeScript errors with proper type casting for sync token handling
  • Performance: Added cache read operations during merge - monitor for performance impact in production
  • Risk: Complex JSON manipulation and cache merging logic - thorough testing recommended before production deployment

…pdates (#22366)

Co-authored-by: keith@cal.com <keithwillcode@gmail.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>
@zomars zomars requested a review from a team July 15, 2025 00:10
@zomars zomars requested a review from a team as a code owner July 15, 2025 00:10
Copy link

vercel bot commented Jul 15, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

2 Skipped Deployments
Name Status Preview Comments Updated (UTC)
cal ⬜️ Ignored (Inspect) Visit Preview Jul 22, 2025 11:44pm
cal-eu ⬜️ Ignored (Inspect) Visit Preview Jul 22, 2025 11:44pm

Copy link
Contributor

coderabbitai bot commented Jul 15, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

"""

Walkthrough

This update introduces incremental synchronization for Google Calendar availability caching using sync tokens. It adds new methods for incremental event fetching, cache management, and sibling calendar refresh. The database schema and cache repository are extended to store sync tokens. Comprehensive unit and integration tests validate incremental sync, cache merging, and sibling refresh behaviors.

Changes

Files/Paths Change Summary
.../googlecalendar/api/webhook.ts Webhook handler updated to use incremental sync method if available, else fallback to full cache refresh.
.../googlecalendar/lib/CalendarService.ts Implements incremental sync, cache merging, sync token storage, and sibling refresh methods in calendar service.
.../googlecalendar/lib/mocks/googleapis.ts Adds mock for Google Calendar API events.list supporting sync tokens.
.../googlecalendar/lib/tests/CalendarService.test.ts Adds extensive tests for incremental sync, cache merging, error handling, and webhook integration.
.../googlecalendar/lib/tests/google-calendar-sync-tokens.integration-test.ts
.../round-robin-individual-caches.integration-test.ts
.../sibling-refresh.integration-test.ts
Adds new integration test suites for sync tokens, cache merging, and sibling refresh logic.
.../calendar-cache/calendar-cache.repository.interface.ts Updates cache repository interface to accept optional nextSyncToken parameter.
.../calendar-cache/calendar-cache.repository.ts Stores nextSyncToken in cache and extends cache expiration to 90 days.
.../prisma/migrations/.../migration.sql Database migration: adds nextSyncToken column to CalendarCache table.
.../prisma/schema.prisma Adds optional nextSyncToken field to CalendarCache model.
.../types/Calendar.d.ts Adds optional fetchAvailabilityAndSetCacheIncremental method to Calendar interface.

Suggested labels

caldav

Poem

🐇✨
A hop, a skip, a sync token found,
Calendars refreshed as caches abound.
Siblings unite, their busy times shared,
With tokens and tests, the service prepared.
Three months of memory, no event left behind—
This rabbit’s delighted with calendars aligned!
📅💫
"""


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot added the ❗️ migrations contains migration files label Jul 15, 2025
@keithwillcode keithwillcode added core area: core, team members only foundation labels Jul 15, 2025
@zomars zomars added ready-for-e2e and removed core area: core, team members only foundation labels Jul 15, 2025
@dosubot dosubot bot added calendar-apps area: calendar, google calendar, outlook, lark, microsoft 365, apple calendar ✨ feature New feature or request labels Jul 15, 2025
@keithwillcode keithwillcode added core area: core, team members only foundation labels Jul 15, 2025
@vercel vercel bot temporarily deployed to Preview – cal July 15, 2025 00:12 Inactive
@vercel vercel bot temporarily deployed to Preview – api July 15, 2025 00:12 Inactive
Copy link

delve-auditor bot commented Jul 15, 2025

No security or compliance issues detected. Reviewed everything up to 494e7bf.

Security Overview
  • 🔎 Scanned files: 13 changed file(s)
Detected Code Changes

The diff is too large to display a summary of code changes.

Reply to this PR with @delve-auditor followed by a description of what change you want and we'll auto-submit a change to this PR to implement it.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🔭 Outside diff range comments (1)
packages/app-store/googlecalendar/lib/__tests__/CalendarService.test.ts (1)

764-764: Remove debug console.log statements.

Debug logging should be removed from test code.

-    console.log({ calendarCachesAfter });
-      console.log({ error });

Also applies to: 783-783

🧹 Nitpick comments (8)
packages/app-store/googlecalendar/api/webhook.ts (1)

46-50: Improve type safety while maintaining backward compatibility.

The conditional logic correctly implements the incremental sync fallback, but the any type casting reduces type safety.

Consider using a type guard or interface method checking instead:

-  if ((calendarServiceForCalendarCache as any)?.fetchAvailabilityAndSetCacheIncremental) {
-    await (calendarServiceForCalendarCache as any).fetchAvailabilityAndSetCacheIncremental(selectedCalendars);
+  if ('fetchAvailabilityAndSetCacheIncremental' in calendarServiceForCalendarCache) {
+    await calendarServiceForCalendarCache.fetchAvailabilityAndSetCacheIncremental(selectedCalendars);
   } else {
     await calendarServiceForCalendarCache?.fetchAvailabilityAndSetCache?.(selectedCalendars);
   }

This approach maintains the same functionality while providing better type safety.

packages/app-store/googlecalendar/lib/__tests__/sibling-refresh.integration-test.ts (1)

355-355: Consider increasing the performance threshold to reduce test flakiness.

The 1-second threshold for database operations might be too strict and could cause intermittent test failures in CI/CD environments with variable performance.

-      expect(duration).toBeLessThan(1000); // Should complete within 1 second
+      expect(duration).toBeLessThan(5000); // Should complete within 5 seconds
packages/app-store/googlecalendar/lib/__tests__/round-robin-individual-caches.integration-test.ts (1)

501-501: Consider increasing the performance threshold for consistency.

Similar to the sibling refresh tests, the 1-second threshold might be too strict.

-      expect(duration).toBeLessThan(1000); // Should complete within 1 second
+      expect(duration).toBeLessThan(5000); // Should complete within 5 seconds
packages/app-store/googlecalendar/lib/__tests__/google-calendar-sync-tokens.integration-test.ts (2)

39-57: Consider removing redundant pre-test cleanup.

The cleanup operations in beforeEach appear redundant since afterEach already handles data cleanup. This could slow down test execution unnecessarily.

-    // Don't delete seeded data - only clean up specific test records
-    await prisma.selectedCalendar.deleteMany({
-      where: {
-        user: {
-          email: { contains: testUniqueId },
-        },
-      },
-    });
-    await prisma.credential.deleteMany({
-      where: {
-        user: {
-          email: { contains: testUniqueId },
-        },
-      },
-    });
-    await prisma.user.deleteMany({
-      where: {
-        email: { contains: testUniqueId },
-      },
-    });

501-501: Align performance threshold with other test files.

For consistency across all integration tests, consider using the same performance threshold.

-      expect(duration).toBeLessThan(1000); // Should complete within 1 second
+      expect(duration).toBeLessThan(5000); // Should complete within 5 seconds
packages/app-store/googlecalendar/lib/__tests__/CalendarService.test.ts (2)

1773-1797: Consider using dynamic dates in test data for consistency.

The mock data uses hardcoded dates like "2024-03-15" while the helper function getTestDateRange uses dynamic dates based on the current time. This inconsistency could cause issues when tests run at different times or in different time zones.

Consider aligning the mock data with the dynamic date approach:

-  const mockSingleCalendarEvents = [
-    {
-      id: "event1",
-      status: "confirmed",
-      start: { dateTime: "2024-03-15T10:00:00Z" },
-      end: { dateTime: "2024-03-15T11:00:00Z" },
-    },
-    {
-      id: "event2",
-      status: "confirmed",
-      start: { dateTime: "2024-03-15T14:00:00Z" },
-      end: { dateTime: "2024-03-15T15:00:00Z" },
-    },
-  ];
+  const getTestDateRange = () => {
+    const now = new Date();
+    // Use current month boundaries for consistency
+    const timeMin = getTimeMin(now.toISOString());
+    const timeMax = getTimeMax(now.toISOString());
+
+    // Use specific dates within the current month for event testing
+    const testStart = new Date(now.getFullYear(), now.getMonth(), 15, 10, 0, 0);
+    const testEnd = new Date(now.getFullYear(), now.getMonth(), 15, 23, 59, 59);
+
+    return {
+      timeMin,
+      timeMax,
+      testDateFrom: testStart.toISOString(),
+      testDateTo: testEnd.toISOString(),
+    };
+  };
+
+  const { timeMin } = getTestDateRange();
+  const baseDate = timeMin.split("T")[0];
+  
+  const mockSingleCalendarEvents = [
+    {
+      id: "event1",
+      status: "confirmed",
+      start: { dateTime: `${baseDate}T10:00:00Z` },
+      end: { dateTime: `${baseDate}T11:00:00Z` },
+    },
+    {
+      id: "event2",
+      status: "confirmed",
+      start: { dateTime: `${baseDate}T14:00:00Z` },
+      end: { dateTime: `${baseDate}T15:00:00Z` },
+    },
+  ];

2395-2440: Consider testing through public interface when possible.

While testing the existence of fetchAvailabilityAndSetCacheIncremental is important for webhook integration, accessing private methods with as any makes tests brittle to refactoring.

Consider exposing this method through a public interface if it's meant to be used by webhook handlers, or test the behavior through public methods that use it internally.

packages/app-store/googlecalendar/lib/CalendarService.ts (1)

1229-1266: Consider adding safeguards against cascading sibling refreshes.

While the proactive sibling refresh is a clever optimization for ensuring multi-calendar cache availability, be mindful of potential performance impacts with many calendars or high-frequency updates.

Consider implementing:

  1. A refresh cooldown period to prevent rapid successive refreshes
  2. A maximum depth or breadth limit for sibling refreshes
  3. Metrics/logging to monitor refresh patterns in production

The current implementation with cache status checks should prevent infinite loops, but monitoring would help ensure this optimization doesn't become a bottleneck.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9631f08 and 38871e4.

📒 Files selected for processing (12)
  • packages/app-store/googlecalendar/api/webhook.ts (1 hunks)
  • packages/app-store/googlecalendar/lib/CalendarService.ts (5 hunks)
  • packages/app-store/googlecalendar/lib/__mocks__/googleapis.ts (1 hunks)
  • packages/app-store/googlecalendar/lib/__tests__/CalendarService.test.ts (1 hunks)
  • packages/app-store/googlecalendar/lib/__tests__/google-calendar-sync-tokens.integration-test.ts (1 hunks)
  • packages/app-store/googlecalendar/lib/__tests__/round-robin-individual-caches.integration-test.ts (1 hunks)
  • packages/app-store/googlecalendar/lib/__tests__/sibling-refresh.integration-test.ts (1 hunks)
  • packages/features/calendar-cache/calendar-cache.repository.interface.ts (1 hunks)
  • packages/features/calendar-cache/calendar-cache.repository.ts (3 hunks)
  • packages/prisma/migrations/20250711215639_add_next_sync_token_to_calendar_cache/migration.sql (1 hunks)
  • packages/prisma/schema.prisma (1 hunks)
  • packages/types/Calendar.d.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
packages/features/calendar-cache/calendar-cache.repository.ts (1)
packages/features/calendar-cache/calendar-cache.repository.interface.ts (1)
  • FreeBusyArgs (5-5)
packages/app-store/googlecalendar/lib/__tests__/sibling-refresh.integration-test.ts (2)
packages/features/calendar-cache/calendar-cache.repository.ts (1)
  • CalendarCacheRepository (51-176)
packages/features/calendar-cache/lib/datesForCache.ts (2)
  • getTimeMin (2-8)
  • getTimeMax (21-59)
packages/app-store/googlecalendar/lib/CalendarService.ts (3)
packages/types/Calendar.d.ts (1)
  • EventBusyDate (56-60)
packages/features/calendar-cache/lib/datesForCache.ts (2)
  • getTimeMin (2-8)
  • getTimeMax (21-59)
packages/features/calendar-cache/calendar-cache.ts (1)
  • CalendarCache (14-30)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Security Check
🔇 Additional comments (16)
packages/app-store/googlecalendar/lib/__mocks__/googleapis.ts (1)

27-32: LGTM! Clean mock implementation for testing incremental sync.

The mock correctly returns the expected Google Calendar API response structure with items and nextSyncToken, which supports testing of the new incremental synchronization functionality.

packages/prisma/migrations/20250711215639_add_next_sync_token_to_calendar_cache/migration.sql (1)

1-2: LGTM! Clean migration to support sync tokens.

The migration correctly adds an optional nextSyncToken TEXT column to support incremental calendar synchronization. Making it nullable ensures backward compatibility with existing cache entries.

packages/types/Calendar.d.ts (1)

297-297: LGTM! Well-designed interface extension.

The new optional method follows the existing interface patterns and maintains backward compatibility while enabling incremental calendar synchronization functionality.

packages/prisma/schema.prisma (1)

720-720: LGTM! Schema correctly aligned with migration.

The nextSyncToken field is properly defined as an optional String, matching the migration and supporting the incremental calendar synchronization feature.

packages/features/calendar-cache/calendar-cache.repository.interface.ts (1)

15-15: LGTM! Clean interface extension for sync token support.

The optional nextSyncToken parameter is properly typed and aligns well with the incremental sync feature implementation.

Also applies to: 21-21

packages/features/calendar-cache/calendar-cache.repository.ts (2)

15-16: Verify the implications of extending cache duration to 3 months.

The cache duration has been extended from 30 days to 90 days. While this reduces API calls and improves performance, it may lead to:

  • Increased storage requirements
  • Potentially stale calendar data for users who don't receive webhook updates
  • Longer retention of deleted or modified events

Please ensure this aligns with your data freshness requirements and storage capacity.


137-175: Well-implemented sync token storage!

The nextSyncToken is correctly integrated into both update and create operations, maintaining consistency with the interface changes.

packages/app-store/googlecalendar/lib/__tests__/sibling-refresh.integration-test.ts (1)

359-514: Excellent test coverage for cache refresh coordination!

The tests comprehensively cover:

  • Cache coordination across sibling calendars
  • Sync token persistence and retrieval
  • Different time range scenarios

Well-structured integration tests that validate the sibling refresh feature.

packages/app-store/googlecalendar/lib/__tests__/CalendarService.test.ts (6)

1818-1995: Comprehensive test coverage for fetchEventsIncremental.

The test suite thoroughly covers sync token functionality including:

  • Initial sync without token
  • Incremental sync with existing token
  • Pagination handling
  • 410 error fallback to full sync
  • Proper error propagation for non-410 errors

Well-structured and follows testing best practices.


1997-2044: Good edge case coverage for event conversion.

The tests properly validate:

  • Standard event conversion
  • Filtering of cancelled events
  • Handling of all-day events (without dateTime)

This ensures robust event processing.


2046-2243: Excellent test coverage for cache operations with sync tokens.

The tests validate:

  • Cache storage with sync tokens
  • Incremental sync using cached tokens
  • Full sync fallback when no token exists
  • Error handling and recovery

The cache integration is well-tested.


2245-2393: Well-designed tests for multi-calendar cache merging fix.

The tests effectively validate that:

  • Individual calendar caches created by incremental sync are properly merged
  • Multi-calendar queries hit the merged cache
  • No unnecessary API calls are made when cache is available

Great use of spies to verify the caching behavior.


2442-2525: Excellent edge case and performance testing.

The tests cover important scenarios:

  • Empty event handling
  • Large dataset performance (1000 events < 1 second)
  • Graceful handling of malformed events

The performance assertion provides a concrete benchmark.


2527-2878: Outstanding test coverage for sibling calendar refresh feature.

These tests thoroughly validate:

  • Proactive sibling calendar refresh during webhook processing
  • Optimization to skip calendars with fresh sync token cache
  • Selective refresh based on cache state (old-style, missing, or fresh)

The tests effectively demonstrate the performance benefits of the optimization.

packages/app-store/googlecalendar/lib/CalendarService.ts (2)

525-544: LGTM!

The convertEventsToBusyTimes method correctly filters out invalid events and converts valid events to the expected format.


705-761: Excellent cache optimization!

The enhanced cache retrieval logic that attempts to merge individual calendar caches when multi-calendar queries miss is a smart performance optimization. This will significantly improve cache hit rates for multi-calendar scenarios.

Copy link
Contributor

github-actions bot commented Jul 15, 2025

E2E results are ready!

zomars added 5 commits July 14, 2025 17:43
Introduced a private fetchEventsPaginated method to handle paginated event retrieval. Updated fetchEventsIncremental to use this helper for both incremental and full resync cases, reducing code duplication and improving maintainability.
@zomars zomars added the high-risk Requires approval by Foundation team label Jul 15, 2025
hariombalhara and others added 20 commits July 22, 2025 09:49
…unit tests (#22264)

* chore: remove unnecessary logs and fix documentation

* refactor: extract GetSlotsInputWithRouting type and eliminate code duplication

- Move GetSlotsInputWithRouting_2024_09_04 type to platform-types package for reuse
- Refactor slots service to eliminate duplicate error handling logic
- Fix TypeScript errors in slots service tests by adding missing type property
- Update test expectations to match new implementation

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: improve type safety in slots service

- Export explicit types from slots-input.service for transformed queries
- Replace 'any' type with proper TransformedSlotsQuery union type
- Re-implement fetchAndFormatSlots abstraction to eliminate code duplication
- Revert unrelated console.log in router.controller.ts

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: simplify slots service implementation

- Remove intermediate variable assignment in getAvailableSlotsWithRouting
- Update test to match simplified routing parameters structure

* test: add comprehensive error handling and edge case tests for slots service

- Add error scenario tests for NotFoundException, invalid time range, and generic errors
- Add edge case tests for null/undefined parameters and empty arrays
- Improve test coverage for getAvailableSlotsWithRouting method
- Mock SlotsInputService properly to enable isolated unit testing

* 📝 CodeRabbit Chat: Rename TransformedGetSlotsQuery types to InternalGetSlotsQuery in slot services

* fix: correct import path for AvailableSlotsService in slots service test

- Fix import path from '@/lib/services/AvailableSlots' to '@/lib/services/available-slots.service'
- Resolves unit test failure due to case sensitivity/naming mismatch
- All API v2 tests now pass (9 test suites, 142 tests)

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
* fix: update enterprise sales URL from i.cal.com to go.cal.com

- Update EnterprisePage contact sales button URL
- Update TeamList admin tools learn more URL
- Update platform pricing enterprise redirect URL
- Update documentation example URL
- All URLs now point to go.cal.com/enterprise

Co-Authored-By: peer@cal.com <peer@cal.com>

* Apply suggestions from code review

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…oss (#22410)

Co-authored-by: eunjae@cal.com <hey@eunjae.dev>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
* feat: add 2FA status column to organization members table

- Add twoFactorEnabled column to UserListTable with admin-only visibility
- Modify backend handler to conditionally include 2FA data for org admins
- Set column as hidden by default using initalColumnVisibility
- Use Badge component to display enabled/disabled status
- Implement proper permission checks using adminOrOwner variable

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: hide 2FA column from display options for non-admin users

- Change enableHiding from true to adminOrOwner
- Prevents non-admin users from seeing 2FA column in column visibility menu
- Maintains existing cell-level permission checks for data access

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: resolve CI workflow dependency mismatch for integration-test job

- Remove build job dependencies from integration-test job that were causing skipped job failures
- integration-test now depends on [changes, check-label, deps] instead of build jobs
- This fixes the 'required' check failure where skipped conditional jobs were treated as failures

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* revert: restore integration-test job dependencies as requested by keithwillcode

- Restore integration-test dependencies to [changes, check-label, build, build-api-v1, build-api-v2]
- Add conditional logic to only run integration-test when run-e2e == 'true'
- This prevents the 'required' check from failing due to skipped conditional jobs
- Addresses GitHub comment from keithwillcode requesting workflow revert

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* revert: remove conditional logic from integration-test job as requested

- Remove run-e2e condition from integration-test job
- Restore original workflow exactly as it was before any modifications
- Addresses GitHub comment from keithwillcode requesting no CI changes

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…credit usage (#22567)

* feat: add test to verify unpublished platform orgs are excluded from credit usage

- Add filtering logic in _getTeamWithAvailableCredits to skip unpublished platform organizations
- Add comprehensive test case that verifies user with memberships in both unpublished platform org and regular team
- Ensure only regular team is selected for credit usage, not unpublished platform org
- Test validates team.findUnique calls and credit balance checks for correct team filtering

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* Update credit-service.ts

* Update credit-service.test.ts

* fix: update test to work with repository-level filtering for unpublished platform orgs

- Remove obsolete team.findUnique mocking since filtering moved to repository layer
- Update test to mock findAllAcceptedPublishedTeamMemberships correctly
- Test now validates integration with repository-level filtering
- Maintains core validation that unpublished platform orgs are excluded from credit usage

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* docs: improve test comments to clarify repository-level filtering scenario

- Add clearer comments explaining user has memberships in both teams
- Clarify that repository method filters out unpublished platform orgs
- Better document the architectural change from PR #22600

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* docs: clarify test demonstrates user with memberships in both teams

- Add detailed comments explaining user has memberships in TWO teams initially
- Clarify that unpublished platform org (teamId: 1) and regular team (teamId: 2) scenario
- Explain repository-level filtering removes unpublished platform orgs before credit service
- Address user feedback about demonstrating two-team membership scenario

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* docs: add comprehensive comments explaining two-team filtering scenario

- Clarify that user has memberships in BOTH teams initially in database
- Explain that repository method filters out unpublished platform org (teamId: 1)
- Document that only regular team (teamId: 2) is returned to credit service
- Address user feedback about demonstrating the filtering behavior clearly
- Test validates integration with repository-level filtering from PR #22600

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* fix: update test to properly demonstrate filtering behavior

- Mock repository to return only regular team (teamId: 2) as expected after filtering
- Update credit balance mock and expectations to match teamId: 2
- Test now passes and validates that unpublished platform org is filtered out
- Address user feedback about showing correct filtering behavior in test logic

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* feat: demonstrate two-team membership scenario in test logic

- Mock repository to return both membership objects: teamId 1 and teamId 2
- Show user has memberships in both unpublished platform org and regular team
- Mock different credit states: teamId 1 has no credits/limit reached, teamId 2 has available credits
- Verify both teams are processed but only regular team (teamId 2) is selected
- Test now demonstrates actual filtering behavior in test logic, not just comments
- Addresses user feedback: 'create two membership with those two teams in that test'

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* feat: demonstrate two-team membership scenario in test logic

- Mock repository to return both membership objects: teamId 1 and teamId 2
- Show user has memberships in both unpublished platform org and regular team
- Mock different credit states: teamId 1 has no credits/limit reached, teamId 2 has available credits
- Verify both teams are processed but only regular team (teamId 2) is selected
- Test now demonstrates actual filtering behavior in test logic, not just comments
- Addresses user feedback: 'create two membership with those two teams in that test'

Co-Authored-By: anik@cal.com <adhabal2002@gmail.com>

* Revert "feat: demonstrate two-team membership scenario in test logic"

This reverts commit d7c4beb.

* Update credit-service.test.ts

* Update credit-service.test.ts

* Update credit-service.test.ts

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* feat: add hourly booking charts on /insights

* safe guard

* rename

* update styles

* fix data

* clean up

* clean up

* re-order charts

* update style

* apply feedback

* rename

* update query
)

* feat: include record IDs in Salesforce assignment reason strings

- Add recordId parameter to assignmentReasonHandler function
- Include Contact ID, Lead ID, and Account ID in assignment reason strings
- Update entire call chain to pass record IDs from CRM service
- Maintain backward compatibility with optional recordId parameter

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: resolve lint warnings in assignment reason handler implementation

- Change Record<string, any> to Record<string, unknown> in BookingHandlerInput type
- Remove unused eventTypeId variable in getAttributeRoutingConfig function

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: revert to Record<string, any> with ESLint disable for BookingHandlerInput

- Revert from Record<string, unknown> to Record<string, any> to maintain type compatibility
- Add ESLint disable comment to suppress no-explicit-any warning
- Maintains consistency with handleNewRecurringBooking.ts pattern

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* feat: pass CRM record ID from booker state to handleNewBooking

- Add crmRecordId field to booker store interface and initialization
- Update mapBookingToMutationInput to include record ID from booker state
- Modify handleNewBooking to extract record ID from bookingData parameter
- Add crmRecordId to BookingCreateBody schema in Prisma layer
- Follow existing pattern for CRM fields (teamMemberEmail, crmOwnerRecordType, crmAppSlug)
- Ensures record ID flows: booker store → booking form → mapBookingToMutationInput → handleNewBooking

This replaces the previous backend CRM service extraction approach with
frontend booker state approach as requested by the user.

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* Pass crmRecordId as prop

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Introduces test-gcal-webhooks.sh to start Tunnelmole, extract the public URL, and update GOOGLE_WEBHOOK_URL in the .env file. Handles process management, rate limits, and ensures environment configuration for Google Calendar webhooks.
Replaces 'ts-node' with 'npx tsx' in the dev:cron script for running cron-tester.ts, likely to improve compatibility or leverage tsx features.
* feat: add calendar cache status dropdown

- Add updatedAt field to CalendarCache schema with migration
- Create tRPC cacheStatus endpoint for fetching cache timestamps
- Add action dropdown to CalendarSwitch for Google Calendar entries
- Display formatted last updated timestamp in dropdown
- Add placeholder for cache deletion functionality
- Include translation strings for dropdown content

The dropdown only appears for Google Calendar integrations that have
active cache entries and provides cache management options for future
extensibility.

Co-Authored-By: zomars@cal.com <zomars@me.com>

* fix: resolve Prisma type incompatibilities in repository files

- Remove problematic satisfies clause in selectedCalendar.ts
- Add missing cacheStatus parameter to ConnectedCalendarList component
- Fixes type errors that were preventing CI from passing

Co-Authored-By: zomars@cal.com <zomars@me.com>

* refactor: integrate cache status into connectedCalendars handler

- Remove separate cacheStatus tRPC endpoint as requested
- Return cache status as separate field in connectedCalendars response
- Update UI components to use cache data from connectedCalendars
- Fix Prisma type incompatibilities in repository files

Co-Authored-By: zomars@cal.com <zomars@me.com>

* fix: resolve Prisma type incompatibilities and fix data flow for cache status

- Fix Prisma.SortOrder usage in membership.ts orderBy clauses
- Remove problematic satisfies clause in selectedCalendar.ts
- Fix TeamSelect type reference in team.ts
- Update SelectedCalendarsSettingsWebWrapper to properly pass cacheStatus data flow

Co-Authored-By: zomars@cal.com <zomars@me.com>

* Discard changes to packages/lib/server/repository/membership.ts

* Discard changes to packages/lib/server/repository/team.ts

* fix: improve calendar cache dropdown with proper formatting and subscription logic

- Fix timestamp HTML entity encoding with interpolation escapeValue: false
- Only show dropdown for subscribed Google calendars (googleChannelId exists)
- Hide delete option when no cache data exists
- Include updatedAt and googleChannelId fields upstream in user repository
- Update data flow to pass subscription status through components

Co-Authored-By: zomars@cal.com <zomars@me.com>

* feat: update SelectedCalendar.updatedAt when Google webhooks trigger cache refresh

- Add updateManyByCredentialId method to SelectedCalendarRepository
- Update fetchAvailabilityAndSetCache to refresh SelectedCalendar timestamps
- Ensure webhook flow updates both CalendarCache and SelectedCalendar records
- Maintain proper timestamp tracking for calendar cache operations

Co-Authored-By: zomars@cal.com <zomars@me.com>

* Add script to automate Tunnelmole webhook setup

Introduces test-gcal-webhooks.sh to start Tunnelmole, extract the public URL, and update GOOGLE_WEBHOOK_URL in the .env file. Handles process management, rate limits, and ensures environment configuration for Google Calendar webhooks.

* Update dev:cron script to use npx tsx

Replaces 'ts-node' with 'npx tsx' in the dev:cron script for running cron-tester.ts, likely to improve compatibility or leverage tsx features.

* Update cache status string and improve CalendarSwitch UI

Renamed 'last_updated' to 'cache_last_updated' in locale file for clarity and updated CalendarSwitch to use the new string. Also added dark mode text color support for cache status display.

* refactor: move cache management to credential-level dropdown with Remove App

- Create CredentialActionsDropdown component consolidating cache and app removal actions
- Add deleteCache tRPC mutation for credential-level cache deletion
- Update connectedCalendars handler to include cacheUpdatedAt at credential level
- Move dropdown from individual CalendarSwitch to credential level in SelectedCalendarsSettingsWebWrapper
- Remove cache-related props from CalendarSwitch component
- Add translation strings for cache management actions
- Consolidate all credential-level actions (cache management + Remove App) in one dropdown

Co-Authored-By: zomars@cal.com <zomars@me.com>

* fix: remove duplicate translation keys in common.json

- Remove duplicate cache-related keys at lines 51-56
- Keep properly positioned keys later in file
- Addresses GitHub comment from zomars about duplicate keys

Co-Authored-By: zomars@cal.com <zomars@me.com>

* fix: rename translation key to cache_last_updated

- Address GitHub comment from zomars
- Rename 'last_updated' to 'cache_last_updated' for specificity
- Update usage in CredentialActionsDropdown component

Co-Authored-By: zomars@cal.com <zomars@me.com>

* fix: remove duplicate last_updated translation key

Co-Authored-By: zomars@cal.com <zomars@me.com>

* fix: add confirmation dialog for cache deletion and use repository pattern

- Add confirmation dialog for destructive cache deletion action
- Replace direct Prisma calls with CalendarCacheRepository pattern
- Add getCacheStatusByCredentialIds method to repository interface
- Fix import paths for UI components
- Address GitHub review comments from zomars

Co-Authored-By: zomars@cal.com <zomars@me.com>

* Update CredentialActionsDropdown.tsx

* Update common.json

* Update common.json

* fix: remove nested div wrapper to resolve HTML structure error

- Remove wrapping div around DisconnectIntegration component
- Fixes nested <p> tag validation error preventing Remove App functionality
- Maintains existing confirmation dialog patterns

Co-Authored-By: zomars@cal.com <zomars@me.com>

* Fix API handler response termination logic

Removed unnecessary return values after setting status in the integrations API handler. This clarifies response handling and prevents returning the response object when not needed. Resolves "API handler should not return a value, received object".

* fix: 400 is correct error code for computing slot for past booking (#22574)

* fix

* add test

* chore: release v5.5.1

* Refactor credential disconnect to use confirmation dialog

Replaces the DisconnectIntegration component with an inline confirmation dialog for removing app credentials. Adds disconnect mutation logic and updates UI to improve user experience and consistency.

* Set default value for CalendarCache.updatedAt

Added a default value of NOW() for the updatedAt column in the CalendarCache table to ensure existing and future rows have a valid timestamp. Updated the Prisma schema to reflect this change and provide compatibility for legacy data and raw inserts.

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Benny Joo <sldisek783@gmail.com>
Co-authored-by: emrysal <me@alexvanandel.com>
devin-ai-integration bot and others added 3 commits July 22, 2025 22:47
- Add CalendarEvent and CalendarSyncToken models to Prisma schema
- Implement CalendarCacheSqlRepository for SQL-based caching
- Create CalendarCacheDualRepository for gradual migration
- Add team-based feature flags: calendar-cache-sql-write and calendar-cache-sql-read
- Maintain backward compatibility with existing JSON cache
- Enable gradual SQL cache population without migrations
- Update CalendarService to pass team context for feature flag checks
- Fix linting issues: replace 'any' types with proper type definitions

Co-Authored-By: zomars@cal.com <zomars@me.com>
- Add CalendarEvent and CalendarSyncToken tables to Prisma schema
- Create CalendarCacheSqlRepository for SQL-based event storage
- Implement CalendarCacheDualRepository for gradual migration
- Add calendar-cache-sql-write and calendar-cache-sql-read feature flags
- Support team-based feature flag control for opt-in rollout
- Maintain backward compatibility with existing JSON cache
- Enable gradual SQL cache population without migrations
- Update CalendarService to pass team context for feature flags
- Add integration test structure for SQL cache functionality

Co-Authored-By: zomars@cal.com <zomars@me.com>
…errors

- Fix duplicate getCacheStatusByCredentialIds method in CalendarCacheDualRepository
- Ensure proper interface implementation for ICalendarCacheRepository
- Update CalendarService.test.ts to handle optional EventBusyDate fields
- Maintain backward compatibility with existing JSON cache functionality

Co-Authored-By: zomars@cal.com <zomars@me.com>
@zomars
Copy link
Contributor Author

zomars commented Jul 23, 2025

Closing in favor of #22708

@zomars zomars closed this Jul 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
calendar-apps area: calendar, google calendar, outlook, lark, microsoft 365, apple calendar core area: core, team members only ✨ feature New feature or request foundation high-risk Requires approval by Foundation team ❗️ migrations contains migration files ready-for-e2e
Projects
None yet
Development

Successfully merging this pull request may close these issues.