Repository: SwitchbackTech/compass
Pull request #339 (2025-04-06 13:41:35)

🧹 Chore: Cleanup Recurrence Code
Predecessor to #321 

Adds improvements to how recurrences are handled in Compass backend, including:
- adding test fixtures for google calendar event payloads
- update queries
- adding utilities
- updating types
- refactoring how google notifications are routed in the controller

These changes were salvaged from #333. That PR included this generic improves as well as a flawed implementation for the issue. Extracting this code in a separate PR so we can use it in the follow-up PR that actually addresses #321 

----
This pull request includes multiple changes to enhance the backend testing utilities, particularly for mocking Google Calendar events and errors. The most important changes include the addition of mock factories for recurring events, the creation of mock error objects for Google Calendar API errors, and the modification of the test database setup function.

### Enhancements to Mock Factories:

* [`packages/backend/src/__tests__/mocks.ccal/ccal.event.factory.ts`](diffhunk://#diff-80a8bb02917925bbe535af59af5176ace7f1957a641fa07a5e10918363bb43dbR1-R94): Added functions to create mock base recurring events, recurring event instances, and series of recurring event instances.
* [`packages/backend/src/__tests__/mocks.gcal/mocks.gcal/factories/gcal.event.factory.ts`](diffhunk://#diff-a37516e8416234709ebed8721f830c3e5d70d9ef2ac3809dc368df401db06164R1-R123): Added functions to create mock regular and recurring Google Calendar events, including instances of recurring events.
* [`packages/backend/src/__tests__/mocks.gcal/mocks.gcal/factories/gcal.factory.ts`](diffhunk://#diff-f8fbddd7a8ec739e8b09321da10600683824f84f8cc19e6fa47cc4d201d1b622R1-R117): Added functions to generate paginated events and mock Google Calendar API responses.

### Addition of Mock Error Objects:

* [`packages/backend/src/__tests__/mocks.gcal/mocks.gcal/errors/error.google.invalidGrant.ts`](diffhunk://#diff-dd78172501736ba876ba63f4ae5f4e9057a123214a1cc0b415680370dc51a168R1-R25): Added a mock error object for the "invalid_grant" error from the Google Calendar API.
* [`packages/backend/src/__tests__/mocks.gcal/mocks.gcal/errors/error.google.invalidValue.ts`](diffhunk://#diff-dc1402dc00f0589212083e99c95bf34700617f1c34db0a639eca04e6f648cd6aR1-R108): Added a mock error object for the "Invalid Value" error from the Google Calendar API.
* [`packages/backend/src/__tests__/mocks.gcal/mocks.gcal/errors/error.invalidSyncToken.ts`](diffhunk://#diff-b15856b7afdc2f7a9a5ca8ad3585d1fcb1ffa84248db0b7b050a7021019c00f8R1-R128): Added a mock error object for the "invalid sync token" error from the Google Calendar API.

### Minor Changes:

* [`packages/backend/src/__tests__/backend.test.start.js`](diffhunk://#diff-c07bc60631a12581e716ed3c5767cb66169749e4988eaa702a6aebbd11e5ce01R4): Added a mock implementation for the `info` method in the logger.
* [`packages/backend/src/__tests__/helpers/mock.db.setup.ts`](diffhunk://#diff-4b1ce4378c7b10e537842c8a178c697ed9f2d34236c860bd6f19b90fd892d2aeL21-R21): Renamed the function `setupTestMongo` to `setupTestDb`.


Patch:
diff --git a/packages/backend/src/event/queries/event.queries.ts b/packages/backend/src/event/queries/event.queries.ts
index 40ff3616..2d2ccf01 100644
--- a/packages/backend/src/event/queries/event.queries.ts
+++ b/packages/backend/src/event/queries/event.queries.ts
@@ -1,38 +1,28 @@
-import { AnyBulkWriteOperation } from "mongodb";
+import { AnyBulkWriteOperation, WithId } from "mongodb";
 import {
   Payload_Order,
   Schema_Event,
   Schema_Event_Core,
 } from "@core/types/event.types";
 import { Collections } from "@backend/common/constants/collections";
-import {
-  EventError,
-  GenericError,
-} from "@backend/common/constants/error.constants";
+import { EventError } from "@backend/common/constants/error.constants";
 import { error } from "@backend/common/errors/handlers/error.handler";
 import { getIdFilter } from "@backend/common/helpers/mongo.utils";
 import mongoService from "@backend/common/services/mongo.service";
 
 type Ids_Event = "_id" | "gEventId";
 
-export const deleteInstances = async (userId: string, baseId: string) => {
-  const response = await mongoService.db
-    .collection(Collections.EVENT)
-    .deleteMany({
-      user: userId,
-      _id: { $ne: mongoService.objectId(baseId) },
-      "recurrence.eventId": baseId,
-    });
-
-  return response;
-};
+/**
+ * DB operations for Compass's Event collection, focused
+ * on primitive operations
+ */
 
 export const findCompassEventBy = async (key: Ids_Event, value: string) => {
   const filter = getIdFilter(key, value);
 
   const event = (await mongoService.db
     .collection(Collections.EVENT)
-    .findOne(filter)) as unknown as Schema_Event;
+    .findOne(filter)) as unknown as WithId<Schema_Event | null>;
 
   return { eventExists: event !== null, event };
 };
@@ -66,44 +56,19 @@ export const updateEvent = async (
     delete _event._id; // mongo doesn't allow changing this field directly
   }
 
-  const response = await mongoService.db
+  const response = (await mongoService.db
     .collection(Collections.EVENT)
     .findOneAndReplace(
       { _id: mongoService.objectId(eventId), user: userId },
       _event,
       { returnDocument: "after" },
-    );
+    )) as unknown as WithId<Schema_Event>;
 
   if (!response) {
-    throw error(EventError.NoMatchingEvent, "Prompt Redux refresh");
-  }
-  return response;
-};
-
-export const updateFutureInstances = async (
-  userId: string,
-  event: Schema_Event,
-) => {
-  const baseId = event.recurrence?.eventId;
-  if (!baseId) {
     throw error(
-      GenericError.BadRequest,
-      "Failed to update future instances (No base event id)",
+      EventError.NoMatchingEvent,
+      "Event not updated due to failed DB operation",
     );
   }
-
-  // eslint-disable-next-line @typescript-eslint/no-unused-vars
-  const { _id, startDate, endDate, user, ...eventWithEligibleFields } = event;
-
-  const futureInstances = {
-    user: userId,
-    "recurrence.eventId": baseId,
-    startDate: { $gt: event.startDate },
-  };
-
-  const response = await mongoService.db
-    .collection(Collections.EVENT)
-    .updateMany(futureInstances, { $set: eventWithEligibleFields });
-
   return response;
 };
diff --git a/packages/backend/src/event/services/recur/queries/event.recur.queries.ts b/packages/backend/src/event/services/recur/queries/event.recur.queries.ts
new file mode 100644
index 00000000..34a62907
--- /dev/null
+++ b/packages/backend/src/event/services/recur/queries/event.recur.queries.ts
@@ -0,0 +1,127 @@
+import { error } from "console";
+import { ObjectId } from "mongodb";
+import { Schema_Event } from "@core/types/event.types";
+import { Collections } from "@backend/common/constants/collections";
+import { GenericError } from "@backend/common/constants/error.constants";
+import mongoService from "@backend/common/services/mongo.service";
+
+/**
+ * DB operations for Compass's Event collection, focused
+ * on recurring event operations
+ */
+
+export const insertBaseEvent = async (event: Schema_Event) => {
+  const eventWithId = {
+    ...event,
+    _id: event._id ? new ObjectId(event._id) : new ObjectId(),
+  };
+  return mongoService.db.collection(Collections.EVENT).insertOne(eventWithId);
+};
+
+export const insertEventInstances = async (instances: Schema_Event[]) => {
+  const instancesWithIds = instances.map((instance) => ({
+    ...instance,
+    _id: instance._id ? new ObjectId(instance._id) : new ObjectId(),
+  }));
+  return mongoService.db
+    .collection(Collections.EVENT)
+    .insertMany(instancesWithIds);
+};
+
+export const updateBaseEventRecurrence = async (
+  gEventId: string,
+  recurrence: { rule: string[]; eventId: string },
+) => {
+  return mongoService.db
+    .collection(Collections.EVENT)
+    .updateOne({ gEventId }, { $set: { recurrence } });
+};
+
+export const deleteFutureInstances = async (
+  baseEventId: string,
+  fromDate: string,
+) => {
+  return mongoService.db.collection(Collections.EVENT).deleteMany({
+    "recurrence.eventId": baseEventId,
+    startDate: { $gte: fromDate },
+  });
+};
+
+export const deleteAllInstances = async (baseEventId: string) => {
+  return mongoService.db.collection(Collections.EVENT).deleteMany({
+    "recurrence.eventId": baseEventId,
+  });
+};
+
+export const deleteInstance = async (gEventId: string) => {
+  return mongoService.db.collection(Collections.EVENT).deleteOne({
+    gEventId,
+  });
+};
+
+export const deleteInstances = async (userId: string, baseId: string) => {
+  if (typeof baseId !== "string") {
+    throw new Error("Invalid baseId");
+  }
+  const response = await mongoService.db
+    .collection(Collections.EVENT)
+    .deleteMany({
+      user: userId,
+      _id: { $ne: new ObjectId(baseId) },
+      "recurrence.eventId": { $eq: baseId },
+    });
+
+  return response;
+};
+
+export const updateInstance = async (
+  userId: string,
+  instance: Schema_Event,
+) => {
+  if (!instance._id) {
+    throw error(
+      GenericError.BadRequest,
+      `Did not update instance because it has has no _id. (Title: ${instance.title})`,
+    );
+  }
+
+  const response = await mongoService.db
+    .collection(Collections.EVENT)
+    .updateOne(
+      {
+        user: userId,
+        _id: new ObjectId(instance._id),
+      },
+      { $set: instance },
+    );
+
+  return response;
+};
+
+export const updateFutureInstances = async (
+  userId: string,
+  event: Schema_Event,
+) => {
+  const baseId = event.recurrence?.eventId;
+  if (!baseId) {
+    throw error(
+      GenericError.BadRequest,
+      "Failed to update future instances (No base event id)",
+    );
+  }
+
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  const { _id, startDate, endDate, user, ...eventWithEligibleFields } = event;
+
+  const futureInstances = {
+    user: userId,
+    "recurrence.eventId": baseId,
+    startDate: { $gt: event.startDate },
+  };
+
+  const response = await mongoService.db
+    .collection(Collections.EVENT)
+    .updateMany(futureInstances, { $set: eventWithEligibleFields });
+
+  return response;
+};
diff --git a/packages/backend/src/event/services/recur/recur.util.ts b/packages/backend/src/event/services/recur/recur.util.ts
index 4ac0591a..49cc201b 100644
--- a/packages/backend/src/event/services/recur/recur.util.ts
+++ b/packages/backend/src/event/services/recur/recur.util.ts
@@ -4,7 +4,12 @@ import { ObjectId } from "mongodb";
 import { RRule } from "rrule";
 import { RRULE } from "@core/constants/core.constants";
 import { YEAR_MONTH_DAY_FORMAT } from "@core/constants/date.constants";
-import { Schema_Event_Core } from "@core/types/event.types";
+import {
+  Schema_Event,
+  Schema_Event_Core,
+  Schema_Event_Recur_Base,
+  Schema_Event_Recur_Instance,
+} from "@core/types/event.types";
 import { GenericError } from "@backend/common/constants/error.constants";
 import { error } from "@backend/common/errors/handlers/error.handler";
 
@@ -30,6 +35,26 @@ export const assembleInstances = (
 
   return events;
 };
+/**
+ * Base events have an `eventId` and an empty `rule`
+ * @param event
+ * @returns
+ */
+export const isBase = (event: Schema_Event) => {
+  return (
+    event.recurrence?.rule !== undefined &&
+    event.recurrence.eventId === undefined
+  );
+};
+
+/**
+ * Instances have an `eventId` and an empty `rule`
+ * @param event
+ * @returns
+ */
+export const isInstance = (event: Schema_Event) => {
+  return event.recurrence?.eventId && event.recurrence?.rule === undefined;
+};
 
 const _generateInstances = (
   rule: string,
@@ -75,6 +100,96 @@ const _generateInstances = (
   return events;
 };
 
+/**
+ * Generates instances for a recurring event based on its recurrence rule
+ * @param baseEvent The base event with recurrence rule
+ * @param maxInstances Maximum number of instances to generate (default: 100)
+ * @returns Array of events including the base event and its instances
+ */
+export const generateRecurringInstances = (
+  baseEvent: Schema_Event,
+  maxInstances: number = 100,
+): Schema_Event[] => {
+  if (!baseEvent.recurrence?.rule || baseEvent.recurrence.rule.length === 0) {
+    throw error(
+      GenericError.DeveloperError,
+      "Failed to generate recurring events: no recurrence rule provided",
+    );
+  }
+
+  if (!baseEvent.startDate || !baseEvent.endDate) {
+    throw error(
+      GenericError.DeveloperError,
+      "Failed to generate recurring events: missing start or end date",
+    );
+  }
+
+  const baseId = baseEvent._id || new ObjectId().toString();
+  const ruleString = baseEvent.recurrence.rule[0];
+  if (!ruleString) {
+    throw error(
+      GenericError.DeveloperError,
+      "Failed to generate recurring events: invalid recurrence rule",
+    );
+  }
+
+  // Parse the base event dates with dayjs
+  const baseStart = dayjs.utc(baseEvent.startDate);
+  const baseEnd = dayjs.utc(baseEvent.endDate);
+  const duration = baseEnd.diff(baseStart);
+
+  // Create the RRule with the base event's start date
+  const dtstart = baseStart.format("YYYYMMDDTHHmmss");
+  const fullRuleString = `DTSTART=${dtstart}Z\n${ruleString};COUNT=${maxInstances}`;
+  const rule = RRule.fromString(fullRuleString);
+
+  // Get all dates from the rule
+  const dates = rule.all();
+
+  // Generate instances
+  const instances = dates
+    .slice(0, maxInstances)
+    .map((date, index) => {
+      // Skip the first date as it's the base event
+      if (index === 0) return null;
+
+      // Convert the RRule date to dayjs and maintain the same time as the base event
+      const instanceStart = dayjs
+        .utc(date)
+        .hour(baseStart.hour())
+        .minute(baseStart.minute())
+        .second(0);
+
+      const instanceEnd = instanceStart.add(duration);
+
+      const instance: Schema_Event_Recur_Instance = {
+        ...baseEvent,
+        _id: undefined, // Let MongoDB generate the ID
+        startDate: instanceStart.toISOString(),
+        endDate: instanceEnd.toISOString(),
+        recurrence: {
+          eventId: baseId,
+        },
+      };
+
+      return instance;
+    })
+    .filter(
+      (instance): instance is Schema_Event_Recur_Instance => instance !== null,
+    );
+
+  // Create the base event
+  const base: Schema_Event_Recur_Base = {
+    ...baseEvent,
+    _id: baseId,
+    recurrence: {
+      rule: [ruleString],
+    },
+  };
+
+  return [base, ...instances];
+};
+
 const _getDates = (rule: string, nextInstance: Date) => {
   let start: Dayjs = dayjs.utc(nextInstance);
   let end: Dayjs;
diff --git a/packages/backend/src/sync/services/notify/gcal.notification.handler.ts b/packages/backend/src/sync/services/notify/gcal.notification.handler.ts
new file mode 100644
index 00000000..35bb4ea6
--- /dev/null
+++ b/packages/backend/src/sync/services/notify/gcal.notification.handler.ts
@@ -0,0 +1,197 @@
+import { Origin } from "@core/constants/core.constants";
+import { Logger } from "@core/logger/winston.logger";
+import { MapEvent } from "@core/mappers/map.event";
+import { gCalendar, gSchema$Event } from "@core/types/gcal";
+import { Collections } from "@backend/common/constants/collections";
+import { SyncError } from "@backend/common/constants/error.constants";
+import { error } from "@backend/common/errors/handlers/error.handler";
+import gcalService from "@backend/common/services/gcal/gcal.service";
+import mongoService from "@backend/common/services/mongo.service";
+import { updateEvent } from "@backend/event/queries/event.queries";
+import { findCompassEventBy } from "@backend/event/queries/event.queries";
+import eventService from "@backend/event/services/event.service";
+import { getSync, updateSyncTokenFor } from "@backend/sync/util/sync.queries";
+
+const logger = Logger("app:gcal.notification.handler");
+
+export class GCalNotificationHandler {
+  constructor(
+    private gcal: gCalendar,
+    private userId: string,
+  ) {}
+
+  /**
+   * Handle a Google Calendar notification
+   */
+  async handleNotification(payload: {
+    calendarId: string;
+    resourceId: string;
+  }): Promise<string> {
+    const nextSyncToken = await this.getNextSyncTokenForCalendar(payload);
+    const { hasChanges, changes } = await this.getLatestChanges(
+      payload.calendarId,
+      nextSyncToken,
+    );
+
+    if (hasChanges) {
+      console.log("++ processing changes:", changes.length);
+      await this.processEvents(changes);
+      return "CHANGES_PROCESSED";
+    } else {
+      return "NO_CHANGES";
+    }
+  }
+
+  /**
+   * Get the latest changes from Google Calendar using a sync token
+   */
+  private async getLatestChanges(calendarId: string, syncToken: string) {
+    const response = await gcalService.getEvents(this.gcal, {
+      calendarId,
+      syncToken,
+    });
+
+    console.log("++ response after getting latest changes:");
+    console.log(JSON.stringify(response.data, null, 2));
+
+    // If the nextSyncToken matches our current syncToken, we've already processed these changes
+    if (response.data.nextSyncToken === syncToken) {
+      logger.info(
+        `Skipping notification - changes already processed for calendar ${calendarId}`,
+      );
+      return { hasChanges: false, changes: [] };
+    }
+
+    const changes = response.data.items;
+    if (!changes || changes.length === 0) {
+      logger.info(`No changes found for calendar ${calendarId}`);
+      return { hasChanges: false, changes: [] };
+    }
+
+    // Update the sync token in the database
+    if (response.data.nextSyncToken) {
+      await updateSyncTokenFor(
+        "events",
+        this.userId,
+        response.data.nextSyncToken,
+        calendarId,
+      );
+    }
+
+    return { hasChanges: true, changes };
+  }
+
+  private async getNextSyncTokenForCalendar(payload: {
+    calendarId: string;
+    resourceId: string;
+  }) {
+    const sync = await getSync({ resourceId: payload.resourceId });
+    if (!sync) {
+      throw error(
+        SyncError.NoSyncRecordForUser,
+        `Notification not handled because no sync record found for calendarId: ${payload.calendarId}`,
+      );
+    }
+
+    // Get the sync token for this calendar
+    const calendarSync = sync.google.events.find(
+      (event) => event.gCalendarId === payload.calendarId,
+    );
+    if (!calendarSync?.nextSyncToken) {
+      throw error(
+        SyncError.NoSyncToken,
+        `Notification not handled because no sync token found for calendarId: ${payload.calendarId}`,
+      );
+    }
+
+    return calendarSync.nextSyncToken;
+  }
+  async processEvents(events: gSchema$Event[]): Promise<void> {
+    // 1. Categorize events by type
+    const categorizedEvents = this.categorizeEvents(events);
+    logger.debug("Categorized events to process:");
+    logger.debug(JSON.stringify(categorizedEvents, null, 2));
+
+    // 2. Process each category
+    await Promise.all([
+      this.processRegularEvents(categorizedEvents.regular),
+      this.processRecurringEvents(categorizedEvents.recurring),
+    ]);
+  }
+
+  /**
+   * Categorize events into regular and recurring
+   */
+  private categorizeEvents(events: gSchema$Event[]): {
+    regular: gSchema$Event[];
+    recurring: gSchema$Event[];
+  } {
+    return events.reduce(
+      (
+        acc: { regular: gSchema$Event[]; recurring: gSchema$Event[] },
+        event,
+      ) => {
+        if (event.recurrence || event.recurringEventId) {
+          acc.recurring.push(event);
+        } else {
+          acc.regular.push(event);
+        }
+        return acc;
+      },
+      { regular: [], recurring: [] },
+    );
+  }
+
+  /**
+   * Process regular (non-recurring) events
+   */
+  private async processRegularEvents(events: gSchema$Event[]): Promise<void> {
+    if (events.length > 0) {
+      console.log("++ processing regular events", events);
+      const compassEvents = MapEvent.toCompass(
+        this.userId,
+        events,
+        Origin.GOOGLE_IMPORT,
+      );
+
+      // Process each event individually to handle upserts
+      await Promise.all(
+        compassEvents.map(async (event) => {
+          // Try to find existing event by gEventId
+          if (!event.gEventId) {
+            // If no gEventId, create new event
+            await eventService.create(this.userId, event);
+            return;
+          }
+
+          const { eventExists, event: existingEvent } =
+            await findCompassEventBy("gEventId", event.gEventId);
+          if (eventExists) {
+            // Update existing event
+            await updateEvent(this.userId, existingEvent._id as string, event);
+          } else {
+            const _event = {
+              ...event,
+              _id: undefined,
+              updatedAt: new Date(),
+              user: this.userId,
+            };
+            console.log("++ creating new event", _event);
+            // Create new event
+            await mongoService.db
+              .collection(Collections.EVENT)
+              .insertOne(_event);
+          }
+        }),
+      );
+    }
+  }
+
+  /**
+   * Process recurring events from Gcal -> Compass
+   */
+  private async processRecurringEvents(recurringEvents: gSchema$Event[]) {
+    console.log(`Processing recurring events: ${recurringEvents.length}`);
+    return "TODO";
+  }
+}
diff --git a/packages/backend/src/sync/services/notify/gcal.notification.util.ts b/packages/backend/src/sync/services/notify/gcal.notification.util.ts
new file mode 100644
index 00000000..8e8d1cb5
--- /dev/null
+++ b/packages/backend/src/sync/services/notify/gcal.notification.util.ts
@@ -0,0 +1,37 @@
+import { SyncError } from "@backend/common/constants/error.constants";
+import { error } from "@backend/common/errors/handlers/error.handler";
+import { getSync } from "@backend/sync/util/sync.queries";
+
+/**
+ * Get the user ID and Google Calendar ID from a sync payload
+ */
+export const getIdsFromSyncPayload = async (
+  channelId: string,
+  resourceId: string,
+) => {
+  // Get the sync record to find the calendar ID
+  const sync = await getSync({ resourceId });
+  if (!sync) {
+    throw error(
+      SyncError.NoSyncRecordForUser,
+      `Notification not handled because no sync record found for resource ${resourceId}`,
+    );
+  }
+  const userId = sync.user;
+
+  // Find the calendar sync record
+  const calendarSync = sync.google?.events?.find(
+    (event) => event.channelId === channelId,
+  );
+  if (!calendarSync?.gCalendarId) {
+    throw error(
+      SyncError.NoSyncRecordForUser,
+      `Notification not handled because no calendar found for channel ${channelId}`,
+    );
+  }
+
+  return {
+    userId,
+    gCalendarId: calendarSync.gCalendarId,
+  };
+};
diff --git a/packages/backend/src/sync/services/notify/sync.notify.ts b/packages/backend/src/sync/services/notify/sync.notify.ts
deleted file mode 100644
index bbcd57b8..00000000
--- a/packages/backend/src/sync/services/notify/sync.notify.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-import {
-  RESULT_IGNORED,
-  RESULT_NOTIFIED_CLIENT,
-} from "@core/constants/websocket.constants";
-import { Logger } from "@core/logger/winston.logger";
-import { Payload_Sync_Notif } from "@core/types/sync.types";
-import { webSocketServer } from "@backend/servers/websocket/websocket.server";
-import { getSync } from "@backend/sync/util/sync.queries";
-import { getCalendarInfo } from "@backend/sync/util/sync.util";
-import { createSyncImport } from "../import/sync.import";
-
-const logger = Logger("app:sync.notify");
-
-export const ACTIONS_SYNC = {
-  INITIALIZED: "initialized",
-  IGNORED: "ignored",
-  PROCESSED: "processed",
-} as const;
-
-export const WS_RESULT = {
-  UNPROCESSED: "unprocessed",
-  IGNORED: RESULT_IGNORED,
-  NOTIFIED_CLIENT: RESULT_NOTIFIED_CLIENT,
-} as const;
-
-export interface Result_Gcal_Notif {
-  action: (typeof ACTIONS_SYNC)[keyof typeof ACTIONS_SYNC];
-  updated: number;
-  created: number;
-  wsResult: (typeof WS_RESULT)[keyof typeof WS_RESULT];
-}
-
-export class SyncNotificationService {
-  async handleGcalNotification(
-    payload: Payload_Sync_Notif,
-  ): Promise<Result_Gcal_Notif> {
-    let result: Result_Gcal_Notif = {
-      action: ACTIONS_SYNC.IGNORED,
-      updated: 0,
-      created: 0,
-      wsResult: WS_RESULT.UNPROCESSED,
-    };
-
-    const { channelId, resourceId, resourceState } = payload;
-    if (resourceState !== "exists") {
-      logger.info(`Sync initialized for channelId: ${channelId}`);
-      result = { ...result, action: ACTIONS_SYNC.INITIALIZED };
-      return result;
-    }
-
-    const sync = await getSync({ resourceId });
-    if (!sync) {
-      logger.debug(
-        `Ignored notification becasuse no sync for this resourceId: ${resourceId}`,
-      );
-      return result;
-    }
-
-    const { userId, gCalendarId, nextSyncToken } = getCalendarInfo(
-      sync,
-      resourceId,
-    );
-
-    const syncImport = await createSyncImport(userId);
-    const importResult = await syncImport.importEventsByCalendar(
-      userId,
-      gCalendarId,
-      nextSyncToken,
-    );
-
-    // notify user
-    const wsResult = webSocketServer.handleBackgroundCalendarChange(userId);
-
-    return {
-      action: ACTIONS_SYNC.PROCESSED,
-      updated: importResult.updated,
-      created: importResult.created,
-      wsResult,
-    };
-  }
-}
diff --git a/packages/backend/src/sync/services/sync.service.ts b/packages/backend/src/sync/services/sync.service.ts
index 77a2ec4c..c9d686d3 100644
--- a/packages/backend/src/sync/services/sync.service.ts
+++ b/packages/backend/src/sync/services/sync.service.ts
@@ -1,5 +1,6 @@
 import { GaxiosError } from "gaxios";
 import { v4 as uuidv4 } from "uuid";
+import { RESULT_NOTIFIED_CLIENT } from "@core/constants/websocket.constants";
 import { Logger } from "@core/logger/winston.logger";
 import { gCalendar } from "@core/types/gcal";
 import {
@@ -16,6 +17,7 @@ import {
 import { error } from "@backend/common/errors/handlers/error.handler";
 import gcalService from "@backend/common/services/gcal/gcal.service";
 import mongoService from "@backend/common/services/mongo.service";
+import { webSocketServer } from "@backend/servers/websocket/websocket.server";
 import { findCompassUserBy } from "@backend/user/queries/user.queries";
 import {
   deleteWatchData,
@@ -33,7 +35,8 @@ import {
   pruneSync,
   refreshSync,
 } from "./maintain/sync.maintenance";
-import { SyncNotificationService } from "./notify/sync.notify";
+import { GCalNotificationHandler } from "./notify/gcal.notification.handler";
+import { getIdsFromSyncPayload } from "./notify/gcal.notification.util";
 
 const logger = Logger("app:sync.service");
 class SyncService {
@@ -60,11 +63,35 @@ class SyncService {
   };
 
   handleGcalNotification = async (payload: Payload_Sync_Notif) => {
-    console.log("++ handleGcalNotification payload:", payload);
-    const syncNotificationService = new SyncNotificationService();
-    const response =
-      await syncNotificationService.handleGcalNotification(payload);
-    return response;
+    const { channelId, resourceId, resourceState } = payload;
+    if (resourceState !== "exists") {
+      logger.info(`Sync initialized for channelId: ${payload.channelId}`);
+      return "INITIALIZED";
+    }
+
+    const { userId, gCalendarId } = await getIdsFromSyncPayload(
+      channelId,
+      resourceId,
+    );
+
+    // Get the Google Calendar client
+    const gcal = await getGcalClient(userId);
+
+    // Create and use the notification handler
+    const handler = new GCalNotificationHandler(gcal, userId);
+    await handler.handleNotification({
+      calendarId: gCalendarId,
+      resourceId: resourceId,
+    });
+
+    const wsResult = webSocketServer.handleBackgroundCalendarChange(userId);
+    const result =
+      wsResult === RESULT_NOTIFIED_CLIENT
+        ? "PROCESSED AND NOTIFIED CLIENT"
+        : "PROCESSED IN BACKGROUND";
+    console.log(result, " (", userId, ")");
+
+    return result;
   };
 
   importFull = async (
diff --git a/packages/backend/src/sync/util/sync.util.ts b/packages/backend/src/sync/util/sync.util.ts
index 185f1a84..597671ef 100644
--- a/packages/backend/src/sync/util/sync.util.ts
+++ b/packages/backend/src/sync/util/sync.util.ts
@@ -1,11 +1,8 @@
-import { WithId } from "mongodb";
 import { Logger } from "@core/logger/winston.logger";
 import { Schema_Sync } from "@core/types/sync.types";
 import { minutesFromNow } from "@core/util/date.utils";
 import { SYNC_BUFFER_DAYS } from "@backend/common/constants/backend.constants";
 import { ENV } from "@backend/common/constants/env.constants";
-import { SyncError } from "@backend/common/constants/error.constants";
-import { error } from "@backend/common/errors/handlers/error.handler";
 
 const logger = Logger("app:sync.helpers");
 
@@ -14,28 +11,6 @@ const logger = Logger("app:sync.helpers");
  * or multiple parts of the sync service's components
  */
 
-export const getCalendarInfo = (
-  sync: WithId<Schema_Sync>,
-  resourceId: string,
-) => {
-  const matches = sync.google.events.filter((g) => {
-    return g.resourceId === resourceId;
-  });
-
-  if (!matches[0]) {
-    throw error(SyncError.NoSyncRecordForUser, "Sync Failed");
-  }
-
-  const gCalendarId = matches[0].gCalendarId;
-  const nextSyncToken = matches[0].nextSyncToken;
-
-  return {
-    userId: sync.user,
-    gCalendarId,
-    nextSyncToken,
-  };
-};
-
 export const getChannelExpiration = () => {
   const numMin = parseInt(ENV.CHANNEL_EXPIRATION_MIN);
   logExpirationReminder(numMin);
@@ -103,24 +78,3 @@ export const syncExpiresSoon = (expiry: string) => {
   const expiresSoon = expiration < deadline;
   return expiresSoon;
 };
-
-// TODO remove this
-// export const findCalendarId = (resourceId: string, sync: Schema_Sync) => {
-//   const matches = sync.google.events.filter((g) => {
-//     return g.resourceId === resourceId;
-//   });
-
-//   if (matches.length !== 1) {
-//     logger.error(`No calendar has resourceId: ${resourceId}`);
-//   }
-
-//   if (matches.length > 1) {
-//     throw new BaseError(
-//       "Duplicate resourceIds",
-//       `Multiple calendars share resourceId: ${resourceId}`,
-//       Status.BAD_REQUEST,
-//       false,
-//     );
-//   }
-// return matches[0]?.gCalendarId;
-// };
diff --git a/packages/core/src/mappers/map.event.ts b/packages/core/src/mappers/map.event.ts
index f27144df..44572f24 100644
--- a/packages/core/src/mappers/map.event.ts
+++ b/packages/core/src/mappers/map.event.ts
@@ -83,11 +83,13 @@ const _toCompass = (
       ? Priorities.UNASSIGNED
       : (_origPriority as Priority);
 
-  const recurrence =
-    gEvent.recurrence || gEvent.recurringEventId
+  const recurrence = gEvent.recurrence
+    ? {
+        rule: gEvent.recurrence,
+      }
+    : gEvent.recurringEventId
       ? {
-          rule: gEvent.recurrence || [],
-          eventId: gEvent.recurringEventId || gEvent.id,
+          eventId: gEvent.recurringEventId,
         }
       : undefined;
 
diff --git a/packages/core/src/types/event.types.ts b/packages/core/src/types/event.types.ts
index 3174b6ee..4250ff96 100644
--- a/packages/core/src/types/event.types.ts
+++ b/packages/core/src/types/event.types.ts
@@ -54,6 +54,19 @@ export interface Schema_Event {
   user?: string;
 }
 
+export interface Schema_Event_Recur_Base
+  extends Omit<Schema_Event, "recurrence"> {
+  recurrence: {
+    rule: string[]; // No eventId since this is the base recurring event
+  };
+}
+
+export interface Schema_Event_Recur_Instance
+  extends Omit<Schema_Event, "recurrence"> {
+  recurrence: {
+    eventId: string; // No rule since this is an instance of the recurring event
+  };
+}
 export interface Schema_Event_Core extends Schema_Event {
   startDate: string;
   endDate: string;
diff --git a/packages/core/src/util/date.utils.ts b/packages/core/src/util/date.utils.ts
index 60236efc..1b95c56b 100644
--- a/packages/core/src/util/date.utils.ts
+++ b/packages/core/src/util/date.utils.ts
@@ -43,3 +43,15 @@ export const minutesFromNow = (numMin: number, format: string) => {
     return -666;
   }
 };
+
+/**
+ * Extract the UNTIL value from a recurrence date string
+ * @param date - The date to get the until value for
+ * @returns The until value for the base event
+ */
+export const getUntilValue = (date: string) => {
+  // Update the original base event to end before the modified instance
+  const untilDate =
+    new Date(date).toISOString().replace(/[-:]/g, "").split(".")[0] + "Z";
+  return untilDate;
+};


Test patch:
diff --git a/packages/backend/src/__tests__/backend.test.start.js b/packages/backend/src/__tests__/backend.test.start.js
index ef081d79..b94bdc34 100644
--- a/packages/backend/src/__tests__/backend.test.start.js
+++ b/packages/backend/src/__tests__/backend.test.start.js
@@ -1,6 +1,7 @@
 jest.mock("@core/logger/winston.logger", () => {
   const mockLogger = {
     debug: jest.fn(),
+    info: jest.fn(),
     warn: jest.fn(),
     error: jest.fn(),
   };
diff --git a/packages/backend/src/__tests__/helpers/mock.db.setup.ts b/packages/backend/src/__tests__/helpers/mock.db.setup.ts
index 68aca920..c2ffa30b 100644
--- a/packages/backend/src/__tests__/helpers/mock.db.setup.ts
+++ b/packages/backend/src/__tests__/helpers/mock.db.setup.ts
@@ -18,7 +18,7 @@ jest.mock("@backend/common/middleware/supertokens.middleware", () => ({
   getSession: jest.fn(),
 }));
 
-export async function setupTestMongo(): Promise<TestSetup> {
+export async function setupTestDb(): Promise<TestSetup> {
   // Setup in-memory MongoDB
   const mongoServer = await MongoMemoryServer.create();
   const mongoUri = mongoServer.getUri();
diff --git a/packages/backend/src/__tests__/mocks.ccal/ccal.event.factory.ts b/packages/backend/src/__tests__/mocks.ccal/ccal.event.factory.ts
new file mode 100644
index 00000000..c2a58bea
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.ccal/ccal.event.factory.ts
@@ -0,0 +1,94 @@
+import { ObjectId } from "mongodb";
+import { Origin, Priorities } from "@core/constants/core.constants";
+import {
+  Schema_Event_Recur_Base,
+  Schema_Event_Recur_Instance,
+} from "@core/types/event.types";
+
+/**
+ * Creates a base recurring event with default values that can be overridden.
+ * @param overrides - The overrides for the mock base event.
+ * @returns A base recurring event.
+ */
+export const createMockBaseEvent = (
+  overrides: Partial<Schema_Event_Recur_Base> = {},
+): Schema_Event_Recur_Base => {
+  const now = new Date();
+  return {
+    _id: new ObjectId().toString(),
+    title: "Weekly Team Sync",
+    description: "Weekly team meeting",
+    startDate: "2024-03-20T10:00:00Z",
+    endDate: "2024-03-20T11:00:00Z",
+    recurrence: {
+      rule: ["RRULE:FREQ=WEEKLY"],
+    },
+    user: "test-user-id",
+    origin: Origin.GOOGLE,
+    priority: Priorities.WORK,
+    isAllDay: false,
+    isSomeday: false,
+    updatedAt: now,
+    ...overrides,
+  };
+};
+
+/**
+ * Creates a recurring event instance with default values that can be overridden.
+ * @param baseEventId - The ID of the base event this instance belongs to.
+ * @param overrides - The overrides for the mock instance.
+ * @returns A recurring event instance.
+ */
+export const createMockInstance = (
+  baseEventId: string,
+  overrides: Partial<Schema_Event_Recur_Instance> = {},
+): Schema_Event_Recur_Instance => {
+  const now = new Date();
+  return {
+    _id: new ObjectId().toString(),
+    title: "Weekly Team Sync",
+    startDate: "2024-03-27T10:00:00Z",
+    endDate: "2024-03-27T11:00:00Z",
+    recurrence: {
+      eventId: baseEventId,
+    },
+    user: "test-user-id",
+    origin: Origin.GOOGLE,
+    priority: Priorities.WORK,
+    isAllDay: false,
+    isSomeday: false,
+    updatedAt: now,
+    ...overrides,
+  };
+};
+
+/**
+ * Creates a series of recurring event instances.
+ * @param baseEvent - The base event to create instances from.
+ * @param count - The number of instances to create.
+ * @param overrides - Optional overrides for all instances.
+ * @returns An array of event instances.
+ */
+export const createMockInstances = (
+  baseEvent: Schema_Event_Recur_Base,
+  count: number,
+  overrides: Partial<Schema_Event_Recur_Instance> = {},
+): Schema_Event_Recur_Instance[] => {
+  const instances: Schema_Event_Recur_Instance[] = [];
+  const baseDate = new Date(baseEvent.startDate || "2024-03-20T10:00:00Z");
+
+  for (let i = 0; i < count; i++) {
+    const instanceDate = new Date(baseDate);
+    instanceDate.setDate(instanceDate.getDate() + (i + 1) * 7); // Weekly recurrence
+
+    instances.push(
+      createMockInstance(baseEvent._id || "", {
+        startDate: instanceDate.toISOString(),
+        endDate: new Date(instanceDate.getTime() + 3600000).toISOString(), // 1 hour later
+        ...overrides,
+      }),
+    );
+  }
+
+  return instances;
+};
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/errors/error.google.invalidGrant.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/errors/error.google.invalidGrant.ts
new file mode 100644
index 00000000..aa528441
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/errors/error.google.invalidGrant.ts
@@ -0,0 +1,25 @@
+import { GaxiosError } from "googleapis-common";
+
+export const invalidGrant400Error = new GaxiosError(
+  "invalid_grant",
+  {},
+  {
+    config: {
+      method: "POST",
+      body: "'refresh_token=1%2F%2F01F_IBIEZx2TUCgYIARAAGAESNgF-L9IrE_2hx29jFovRvv0eIB_pNYnQsFy8QqSlmsq6nzCENaKrnItvDp72Qg9tF3OtDZrdwAZ&client_id=111711111111-mqq17c111hgpgn907j79kgnse1o0lchk.apps.googleusercontent.com&client_secret=Z8o&grant_type=refresh_token'",
+      data: "refresh_token=1%2F%2F01F_IBIEZx2TUCgYIARAAGAESNgF-L9IrE_2hx29jFovRvv0eIB_pNYnQsFy8QqSlmsq6nzCENaKrnItvDp72Qg9tF3OtDZrdwAZ&client_id=111711111111-mqq17c111hgpgn907j79kgnse1o0lchk.apps.googleusercontent.com&client_secret=Z8o&grant_type=refresh_token",
+      url: "https://oauth2.googleapis.com/token",
+      responseType: "json",
+    },
+    data: {
+      error: "invalid_grant",
+      error_description: "Bad Request",
+    },
+    status: 400,
+    statusText: "Bad Request",
+    headers: {},
+    request: {
+      responseURL: "",
+    },
+  },
+);
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/errors/error.google.invalidValue.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/errors/error.google.invalidValue.ts
new file mode 100644
index 00000000..ff715fbd
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/errors/error.google.invalidValue.ts
@@ -0,0 +1,108 @@
+import { GaxiosError } from "googleapis-common";
+
+export const invalidValueError = new GaxiosError(
+  "Invalid Value",
+  {
+    url: "https://www.googleapis.com/calendar/v3/calendars/foo%40gmail.com/events/watch?syncToken=x",
+    method: "POST",
+    data: {
+      address: "https://foo.yourdomain.app/api/sync/gcal/notifications",
+      expiration: "1675961300771",
+      id: "b69deec8-8526-4504-b376-db83295df0f0",
+      token: "secret",
+      type: "web_hook",
+    },
+    headers: {
+      "x-goog-api-client": "gdcl/6.0.4 gl-node/16.17.0 auth/8.7.0",
+      "Accept-Encoding": "gzip",
+      "User-Agent": "google-api-nodejs-client/6.0.4 (gzip)",
+      Authorization:
+        "Bearer ya29.a0AVvZVsr4Nv76zYXg0JPCTWTEc37yn0qUyDu35T6AGiv9ucvLsbMZWQ_zk5eKa2bBqiiOCHp4go1Wkml1-paDG0Aayaf-4XeQd8nm78STjMYNk7cEXL8K5jlHTO3Um1VJtPm4yW-ful6ytXn22ULPzzxlahwQaCgYKAZISARASFQGbdwaIn48X7svG7sifNvemifZazw0163",
+      "Content-Type": "application/json",
+      Accept: "application/json",
+    },
+    params: { syncToken: "x" },
+    retry: true,
+    body: '{"address":"https://foo.yourdomain.app/api/sync/gcal/notifications","expiration":"1675961300771","id":"b69deec8-8526-4504-b376-db83295df0f0","token":"secret","type":"web_hook"}',
+    responseType: "json",
+    retryConfig: {
+      currentRetryAttempt: 0,
+      retry: 3,
+      httpMethodsToRetry: ["GET", "HEAD", "PUT", "OPTIONS", "DELETE"],
+      noResponseRetries: 2,
+      statusCodesToRetry: [
+        [100, 199],
+        [429, 429],
+        [500, 599],
+      ],
+    },
+  },
+  {
+    config: {
+      url: "https://www.googleapis.com/calendar/v3/calendars/foo%40gmail.com/events/watch?syncToken=x",
+      method: "POST",
+      data: {
+        address: "https://foo.yourdomain.app/api/sync/gcal/notifications",
+        expiration: "1675961300771",
+        id: "b69deec8-8526-4504-b376-db83295df0f0",
+        token: "secret",
+        type: "web_hook",
+      },
+      headers: {
+        "x-goog-api-client": "gdcl/6.0.4 gl-node/16.17.0 auth/8.7.0",
+        "Accept-Encoding": "gzip",
+        "User-Agent": "google-api-nodejs-client/6.0.4 (gzip)",
+        Authorization:
+          "Bearer ya29.a0AVvZVsr4Nv76zYXg0JPCTWTEc37yn0qUyDu35T6AGiv9ucvLsbMZWQ_zk5eKa2bBqiiOCHp4go1Wkml1-paDG0Aayaf-4XeQd8nm78STjMYNk7cEXL8K5jlHTO3Um1VJtPm4yW-ful6ytXn22ULPzzxlahwQaCgYKAZISARASFQGbdwaIn48X7svG7sifNvemifZazw0163",
+        "Content-Type": "application/json",
+        Accept: "application/json",
+      },
+      params: { syncToken: "x" },
+      retry: true,
+      body: '{"address":"https://foo.yourdomain.app/api/sync/gcal/notifications","expiration":"1675961300771","id":"b69deec8-8526-4504-b376-db83295df0f0","token":"secret","type":"web_hook"}',
+      responseType: "json",
+      retryConfig: {
+        currentRetryAttempt: 0,
+        retry: 3,
+        httpMethodsToRetry: ["GET", "HEAD", "PUT", "OPTIONS", "DELETE"],
+        noResponseRetries: 2,
+        statusCodesToRetry: [
+          [100, 199],
+          [429, 429],
+          [500, 599],
+        ],
+      },
+    },
+    data: {
+      error: {
+        errors: [
+          { domain: "global", reason: "invalid", message: "Invalid Value" },
+        ],
+        code: 400,
+        message: "Invalid Value",
+      },
+    },
+    headers: {
+      "alt-svc": 'h3=":443"; ma=2592000,h3-29=":443"; ma=2592000',
+      "cache-control": "no-cache, no-store, max-age=0, must-revalidate",
+      connection: "close",
+      "content-encoding": "gzip",
+      "content-type": "application/json; charset=UTF-8",
+      date: "Thu, 09 Feb 2023 16:42:21 GMT",
+      expires: "Mon, 01 Jan 1990 00:00:00 GMT",
+      pragma: "no-cache",
+      server: "ESF",
+      "transfer-encoding": "chunked",
+      vary: "Origin, X-Origin, Referer",
+      "x-content-type-options": "nosniff",
+      "x-frame-options": "SAMEORIGIN",
+      "x-xss-protection": "0",
+    },
+    status: 400,
+    statusText: "Bad Request",
+    request: {
+      responseURL:
+        "https://www.googleapis.com/calendar/v3/calendars/foo%40gmail.com/events/watch?syncToken=x",
+    },
+  },
+);
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/errors/error.invalidSyncToken.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/errors/error.invalidSyncToken.ts
new file mode 100644
index 00000000..ce4e8183
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/errors/error.invalidSyncToken.ts
@@ -0,0 +1,128 @@
+import { GaxiosError } from "googleapis-common";
+import qs from "qs";
+
+export const invalidSyncTokenError = new GaxiosError(
+  "Sync token is no longer valid, a full sync is required.",
+  {
+    url: "https://www.googleapis.com/calendar/v3/calendars/foo%40gmail.com/events/watch?syncToken=1CKj765-V8_wCEKj765-V8_wCGAUghfra8AE%3D",
+    method: "POST",
+    paramsSerializer: (params) => {
+      return qs.stringify(params, { arrayFormat: "repeat" });
+    },
+    data: {
+      address: "https://foo.yourdomain.app/api/sync/gcal/notifications",
+      expiration: "1675307413038",
+      id: "5af8c0d2-de66-4954-9860-ce43c7c60a22",
+      token: "secret",
+      type: "web_hook",
+    },
+    headers: {
+      "x-goog-api-client": "gdcl/6.0.4 gl-node/16.17.0 auth/8.7.0",
+      "Accept-Encoding": "gzip",
+      "User-Agent": "google-api-nodejs-client/6.0.4 (gzip)",
+      Authorization:
+        "Bearer ya29.a0AVvZVsp98H_YLg9KeAZArECvxgVIMnVOQR_LIqkUMmTDi9qp8GBELy3GiDS0GhMo1mFDwsfJLsC4Ufup_AFi3eIrAPjU8d3FiX5M3wdOBeqXaG6miwlvOJDFd49QPi21nTIzKZojhi17TBhKNDiqLcHTWZl4aCgYKAecSARASFQGbdwaIncVRWWWmV5_XAYKqCOPaPg0163",
+      "Content-Type": "application/json",
+      Accept: "application/json",
+    },
+    params: {
+      syncToken: "1CKj765-V8_wCEKj765-V8_wCGAUghfra8AE=",
+    },
+    validateStatus: (status) => {
+      return (status >= 200 && status < 300) || status === 304;
+    },
+    retry: true,
+    body: '{"address":"https://foo.yourdomain.app/api/sync/gcal/notifications","expiration":"1675307413038","id":"5af8c0d2-de66-4954-9860-ce43c7c60a22","token":"secretToken","type":"web_hook"}',
+    responseType: "json",
+    retryConfig: {
+      currentRetryAttempt: 0,
+      retry: 3,
+      httpMethodsToRetry: ["GET", "HEAD", "PUT", "OPTIONS", "DELETE"],
+      noResponseRetries: 2,
+      statusCodesToRetry: [
+        [100, 199],
+        [429, 429],
+        [500, 599],
+      ],
+    },
+  },
+  {
+    config: {
+      url: "https://www.googleapis.com/calendar/v3/calendars/foo%40gmail.com/events/watch?syncToken=1CKj765-V8_wCEKj765-V8_wCGAUghfra8AE%3D",
+      method: "POST",
+      data: {
+        address: "https://foo.yourdomain.app/api/sync/gcal/notifications",
+        expiration: "1675307413038",
+        id: "5af8c0d2-de66-4954-9860-ce43c7c60a22",
+        token: "secret",
+        type: "web_hook",
+      },
+      headers: {
+        "x-goog-api-client": "gdcl/6.0.4 gl-node/16.17.0 auth/8.7.0",
+        "Accept-Encoding": "gzip",
+        "User-Agent": "google-api-nodejs-client/6.0.4 (gzip)",
+        Authorization:
+          "Bearer ya29.a0AVvZVsp98H_YLg9KeAZArECvxgVIMnVOQR_LIqkUMmTDi9qp8GBELy3GiDS0GhMo1mFDwsfJLsC4Ufup_AFi3eIrAPjU8d3FiX5M3wdOBeqXaG6miwlvOJDFd49QPi21nTIzKZojhi17TBhKNDiqLcHTWZl4aCgYKAecSARASFQGbdwaIncVRWWWmV5_XAYKqCOPaPg0163",
+        "Content-Type": "application/json",
+        Accept: "application/json",
+      },
+      params: {
+        syncToken: "1CKj765-V8_wCEKj765-V8_wCGAUghfra8AE=",
+      },
+      validateStatus: (status) => {
+        return (status >= 200 && status < 300) || status === 304;
+      },
+      retry: true,
+      body: '{"address":"https://foo.yourdomain.app/api/sync/gcal/notifications","expiration":"1675307413038","id":"5af8c0d2-de66-4954-9860-ce43c7c60a22","token":"somesecrettoke","type":"web_hook"}',
+      responseType: "json",
+      retryConfig: {
+        currentRetryAttempt: 0,
+        retry: 3,
+        httpMethodsToRetry: ["GET", "HEAD", "PUT", "OPTIONS", "DELETE"],
+        noResponseRetries: 2,
+        statusCodesToRetry: [
+          [100, 199],
+          [429, 429],
+          [500, 599],
+        ],
+      },
+    },
+    data: {
+      error: {
+        errors: [
+          {
+            domain: "calendar",
+            reason: "fullSyncRequired",
+            message: "Sync token is no longer valid, a full sync is required.",
+            locationType: "parameter",
+            location: "syncToken",
+          },
+        ],
+        code: 410,
+        message: "Sync token is no longer valid, a full sync is required.",
+      },
+    },
+    headers: {
+      "alt-svc": 'h3=":443"; ma=2592000,h3-29=":443"; ma=2592000',
+      "cache-control": "no-cache, no-store, max-age=0, must-revalidate",
+      connection: "close",
+      "content-encoding": "gzip",
+      "content-type": "application/json; charset=UTF-8",
+      date: "Thu, 02 Feb 2023 03:04:13 GMT",
+      expires: "Mon, 01 Jan 1990 00:00:00 GMT",
+      pragma: "no-cache",
+      server: "ESF",
+      "transfer-encoding": "chunked",
+      vary: "Origin, X-Origin, Referer",
+      "x-content-type-options": "nosniff",
+      "x-frame-options": "SAMEORIGIN",
+      "x-xss-protection": "0",
+    },
+    status: 410,
+    statusText: "Gone",
+    request: {
+      responseURL:
+        "https://www.googleapis.com/calendar/v3/calendars/foo%40gmail.com/events/watch?syncToken=1CKj765-V8_wCEKj765-V8_wCGAUghfra8AE%3D",
+    },
+  },
+);
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/factories/ccal.event.factory.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/factories/ccal.event.factory.ts
new file mode 100644
index 00000000..c2a58bea
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/factories/ccal.event.factory.ts
@@ -0,0 +1,94 @@
+import { ObjectId } from "mongodb";
+import { Origin, Priorities } from "@core/constants/core.constants";
+import {
+  Schema_Event_Recur_Base,
+  Schema_Event_Recur_Instance,
+} from "@core/types/event.types";
+
+/**
+ * Creates a base recurring event with default values that can be overridden.
+ * @param overrides - The overrides for the mock base event.
+ * @returns A base recurring event.
+ */
+export const createMockBaseEvent = (
+  overrides: Partial<Schema_Event_Recur_Base> = {},
+): Schema_Event_Recur_Base => {
+  const now = new Date();
+  return {
+    _id: new ObjectId().toString(),
+    title: "Weekly Team Sync",
+    description: "Weekly team meeting",
+    startDate: "2024-03-20T10:00:00Z",
+    endDate: "2024-03-20T11:00:00Z",
+    recurrence: {
+      rule: ["RRULE:FREQ=WEEKLY"],
+    },
+    user: "test-user-id",
+    origin: Origin.GOOGLE,
+    priority: Priorities.WORK,
+    isAllDay: false,
+    isSomeday: false,
+    updatedAt: now,
+    ...overrides,
+  };
+};
+
+/**
+ * Creates a recurring event instance with default values that can be overridden.
+ * @param baseEventId - The ID of the base event this instance belongs to.
+ * @param overrides - The overrides for the mock instance.
+ * @returns A recurring event instance.
+ */
+export const createMockInstance = (
+  baseEventId: string,
+  overrides: Partial<Schema_Event_Recur_Instance> = {},
+): Schema_Event_Recur_Instance => {
+  const now = new Date();
+  return {
+    _id: new ObjectId().toString(),
+    title: "Weekly Team Sync",
+    startDate: "2024-03-27T10:00:00Z",
+    endDate: "2024-03-27T11:00:00Z",
+    recurrence: {
+      eventId: baseEventId,
+    },
+    user: "test-user-id",
+    origin: Origin.GOOGLE,
+    priority: Priorities.WORK,
+    isAllDay: false,
+    isSomeday: false,
+    updatedAt: now,
+    ...overrides,
+  };
+};
+
+/**
+ * Creates a series of recurring event instances.
+ * @param baseEvent - The base event to create instances from.
+ * @param count - The number of instances to create.
+ * @param overrides - Optional overrides for all instances.
+ * @returns An array of event instances.
+ */
+export const createMockInstances = (
+  baseEvent: Schema_Event_Recur_Base,
+  count: number,
+  overrides: Partial<Schema_Event_Recur_Instance> = {},
+): Schema_Event_Recur_Instance[] => {
+  const instances: Schema_Event_Recur_Instance[] = [];
+  const baseDate = new Date(baseEvent.startDate || "2024-03-20T10:00:00Z");
+
+  for (let i = 0; i < count; i++) {
+    const instanceDate = new Date(baseDate);
+    instanceDate.setDate(instanceDate.getDate() + (i + 1) * 7); // Weekly recurrence
+
+    instances.push(
+      createMockInstance(baseEvent._id || "", {
+        startDate: instanceDate.toISOString(),
+        endDate: new Date(instanceDate.getTime() + 3600000).toISOString(), // 1 hour later
+        ...overrides,
+      }),
+    );
+  }
+
+  return instances;
+};
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/factories/gcal.event.factory.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/factories/gcal.event.factory.ts
new file mode 100644
index 00000000..5b74b33d
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/factories/gcal.event.factory.ts
@@ -0,0 +1,123 @@
+import { faker } from "@faker-js/faker";
+import { Origin, Priorities } from "@core/constants/core.constants";
+import { gSchema$Event } from "@core/types/gcal";
+
+export const mockRegularEvent = (): gSchema$Event => ({
+  id: faker.string.uuid(),
+  summary: faker.lorem.sentence(),
+  start: { dateTime: faker.date.future().toISOString() },
+  end: { dateTime: faker.date.future().toISOString() },
+  status: "confirmed",
+});
+
+export const mockRecurringEvent = (
+  overrides: Partial<gSchema$Event> = {},
+): gSchema$Event => ({
+  ...mockRegularEvent(),
+  recurrence: ["RRULE:FREQ=WEEKLY"],
+  ...overrides,
+});
+
+const mockRecurringInstances = (
+  event: gSchema$Event,
+  count: number,
+  repeatIntervalInDays: number,
+): gSchema$Event[] => {
+  if (!event.start?.dateTime || !event.end?.dateTime) {
+    throw new Error("Event must have start and end dates");
+  }
+
+  const startDateTime = event.start.dateTime;
+  const endDateTime = event.end.dateTime;
+  const startTimeZone = event.start.timeZone;
+  const endTimeZone = event.end.timeZone;
+
+  const baseDate = new Date(startDateTime);
+
+  return Array.from({ length: count }, (_, index) => {
+    const instanceDate = new Date(baseDate);
+    instanceDate.setDate(instanceDate.getDate() + index * repeatIntervalInDays);
+
+    const endDate = new Date(endDateTime);
+    endDate.setDate(endDate.getDate() + index * repeatIntervalInDays);
+
+    return {
+      ...event,
+      id: `${event.id}-${index}`,
+      recurringEventId: event.id,
+      start: {
+        dateTime: instanceDate.toISOString(),
+        timeZone: startTimeZone,
+      },
+      end: {
+        dateTime: endDate.toISOString(),
+        timeZone: endTimeZone,
+      },
+    };
+  });
+};
+
+export const mockGcalEvent = (
+  overrides: Partial<gSchema$Event> = {},
+): gSchema$Event => {
+  const id = faker.string.uuid();
+  return {
+    id,
+    summary: faker.lorem.sentence(),
+    status: "confirmed",
+    htmlLink: `https://www.google.com/calendar/event?eid=${id}`,
+    created: faker.date.past().toISOString(),
+    updated: faker.date.recent().toISOString(),
+    start: {
+      dateTime: faker.date.future().toISOString(),
+      timeZone: "America/Chicago",
+    },
+    end: {
+      dateTime: faker.date.future().toISOString(),
+      timeZone: "America/Chicago",
+    },
+    iCalUID: faker.string.uuid() + "@google.com",
+    sequence: 0,
+    extendedProperties: {
+      private: {
+        origin: Origin.GOOGLE_IMPORT,
+        priority: Priorities.UNASSIGNED,
+      },
+    },
+    reminders: {
+      useDefault: true,
+    },
+    eventType: "default",
+    ...overrides,
+  };
+};
+
+export const mockGcalEvents = (repeatIntervalInDays = 7) => {
+  const regularEvent = mockGcalEvent();
+  const cancelledEvent = mockGcalEvent({ status: "cancelled" });
+  const recurringEvent = mockGcalEvent({
+    recurrence: ["RRULE:FREQ=DAILY;INTERVAL=7"],
+  });
+
+  const recurringInstances = mockRecurringInstances(
+    recurringEvent,
+    3,
+    repeatIntervalInDays,
+  );
+
+  const gcalEvents = [
+    regularEvent,
+    cancelledEvent,
+    recurringEvent,
+    ...recurringInstances,
+  ];
+
+  return {
+    gcalEvents,
+    totals: {
+      total: gcalEvents.length,
+      cancelled: 1,
+      recurring: 1 + recurringInstances.length,
+    },
+  };
+};
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/factories/gcal.factory.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/factories/gcal.factory.ts
new file mode 100644
index 00000000..9ad31950
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/factories/gcal.factory.ts
@@ -0,0 +1,117 @@
+import {
+  gSchema$CalendarListEntry,
+  gSchema$Event,
+  gSchema$Events,
+} from "@core/types/gcal";
+
+/**
+ * Generates a paginated events object for the Google Calendar API.
+ *
+ * @param events - The total array of events to be split into pages.
+ * @param nextSyncToken - The next sync token for the events.
+ * @param pageSize - The number of events per page.
+ * @param pageToken - The page token for the events.
+ * @returns The paginated events object.
+ */
+const generatePaginatedEvents = (
+  events: gSchema$Event[],
+  nextSyncToken: string,
+  pageSize: number,
+  pageToken?: string,
+): gSchema$Events => {
+  const startIndex = pageToken ? parseInt(pageToken) : 0;
+  const endIndex = startIndex + pageSize;
+  const pageEvents = events.slice(startIndex, endIndex);
+  const hasMore = endIndex < events.length;
+
+  const page = {
+    items: pageEvents,
+    nextPageToken: hasMore ? endIndex.toString() : undefined,
+    nextSyncToken: hasMore ? undefined : nextSyncToken,
+  };
+  return page;
+};
+
+/**
+ * Generates a mock Google Calendar calendar list entry.
+ *
+ * @param overrides - The overrides for the mock.
+ * @returns The mock Google Calendar calendar list entry.
+ */
+export const createMockCalendarList = (
+  overrides: Partial<gSchema$CalendarListEntry> = {},
+): gSchema$CalendarListEntry => ({
+  id: "test-calendar",
+  primary: true,
+  summary: "Test Calendar",
+  ...overrides,
+});
+
+interface Config_MockGcal {
+  events?: gSchema$Event[];
+  pageSize?: number;
+  nextSyncToken?: string;
+  calendarList?: Partial<gSchema$CalendarListEntry>[];
+  calendarListNextSyncToken?: string;
+}
+/**
+ * Generates a mock Google Calendar API response.
+ *
+ * @param config - The configuration for the mock.
+ * @returns The mock Google Calendar API response.
+ */
+export const mockGcal = ({
+  events = [],
+  pageSize = 3,
+  nextSyncToken = "final-sync-token",
+  calendarList = [createMockCalendarList()],
+  calendarListNextSyncToken = "calendar-list-sync-token",
+}: Config_MockGcal = {}) => {
+  return {
+    google: {
+      calendar: jest.fn().mockReturnValue({
+        events: {
+          list: jest
+            .fn()
+            .mockImplementation(async (params: { pageToken?: string }) => {
+              const eventsPage = generatePaginatedEvents(
+                events,
+                nextSyncToken,
+                pageSize,
+                params.pageToken,
+              );
+
+              return {
+                data: eventsPage,
+              };
+            }),
+          instances: jest
+            .fn()
+            .mockImplementation(async (params: { eventId: string }) => {
+              const { eventId } = params;
+              const instances = Array(3)
+                .fill(null)
+                .map(() => ({
+                  ...events[0],
+                  recurringEventId: eventId,
+                }));
+
+              return {
+                data: {
+                  items: instances,
+                },
+              };
+            }),
+        },
+        calendarList: {
+          list: jest.fn().mockResolvedValue({
+            data: {
+              items: calendarList,
+              nextSyncToken: calendarListNextSyncToken,
+            },
+          }),
+        },
+      }),
+    },
+  };
+};
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/create/create.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/create/create.ts
new file mode 100644
index 00000000..4915fc96
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/create/create.ts
@@ -0,0 +1,54 @@
+import { gSchema$Events } from "@core/types/gcal";
+
+export const createNewRecurringEventPayload: gSchema$Events = {
+  kind: "calendar#events",
+  etag: '"p32fttgkdgag8o0o"',
+  summary: "test.user@gmail.com",
+  description: "",
+  updated: "2025-03-23T10:41:35.252Z",
+  timeZone: "America/Chicago",
+  accessRole: "owner",
+  defaultReminders: [
+    {
+      method: "popup",
+      minutes: 30,
+    },
+  ],
+  nextSyncToken: "CJ_ewo2CoIwDEJ_ewo2CoIwDGAUg79XP3wIo79XP3wI=",
+  items: [
+    {
+      kind: "calendar#event",
+      etag: '"3485452990504510"',
+      id: "68k0p6ackplecqs9fuvbs1fju0",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=NjhrMHA2YWNrcGxlY3FzOWZ1dmJzMWZqdTBfMjAyNTAzMjRUMTIzMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-23T10:41:35.000Z",
+      updated: "2025-03-23T10:41:35.252Z",
+      summary: "🍽️ Dishes",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-03-24T07:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-24T08:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurrence: ["RRULE:FREQ=DAILY"],
+      iCalUID: "68k0p6ackplecqs9fuvbs1fju0@google.com",
+      sequence: 0,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+  ],
+};
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/delete/all/all-1.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/delete/all/all-1.ts
new file mode 100644
index 00000000..7b363d9e
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/delete/all/all-1.ts
@@ -0,0 +1,26 @@
+import { gSchema$Events } from "@core/types/gcal";
+
+export const deleteAllPayload1: gSchema$Events = {
+  kind: "calendar#events",
+  etag: '"p32nsfe61kqioo0o"',
+  summary: "test.user@gmail.com",
+  description: "",
+  updated: "2025-03-25T13:07:46.505Z",
+  timeZone: "America/Chicago",
+  accessRole: "owner",
+  defaultReminders: [
+    {
+      method: "popup",
+      minutes: 30,
+    },
+  ],
+  nextSyncToken: "CK_HuMGmpYwDEK_HuMGmpYwDGAUg24mI4AIo24mI4AI=",
+  items: [
+    {
+      kind: "calendar#event",
+      etag: '"3485816133011294"',
+      id: "6pbua6vjeic3e73gvponeo1qp6",
+      status: "cancelled",
+    },
+  ],
+};
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/delete/all/all-2.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/delete/all/all-2.ts
new file mode 100644
index 00000000..7ed4d09b
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/delete/all/all-2.ts
@@ -0,0 +1,26 @@
+import { gSchema$Events } from "@core/types/gcal";
+
+export const deleteAllPayload2: gSchema$Events = {
+  kind: "calendar#events",
+  etag: '"p33nv5euikqioo0o"',
+  summary: "test.user@gmail.com",
+  description: "",
+  updated: "2025-03-25T13:08:22.211Z",
+  timeZone: "America/Chicago",
+  accessRole: "owner",
+  defaultReminders: [
+    {
+      method: "popup",
+      minutes: 30,
+    },
+  ],
+  nextSyncToken: "CO_yu9KmpYwDEO_yu9KmpYwDGAUg24mI4AIo24mI4AI=",
+  items: [
+    {
+      kind: "calendar#event",
+      etag: '"3485816204423902"',
+      id: "0oije1knkkthb56vr149dub9qa",
+      status: "cancelled",
+    },
+  ],
+};
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/delete/all/index.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/delete/all/index.ts
new file mode 100644
index 00000000..9114c8c6
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/delete/all/index.ts
@@ -0,0 +1,4 @@
+import { deleteAllPayload1 } from "./all-1";
+import { deleteAllPayload2 } from "./all-2";
+
+export const deleteAllPayloads = [deleteAllPayload1, deleteAllPayload2];
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/delete/single/delete.single-2.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/delete/single/delete.single-2.ts
new file mode 100644
index 00000000..b8aba1c3
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/delete/single/delete.single-2.ts
@@ -0,0 +1,65 @@
+import { gSchema$Events } from "@core/types/gcal";
+
+export const deleteSinglePayload2: gSchema$Events = {
+  kind: "calendar#events",
+  etag: '"p32nv5rnfmaioo0o"',
+  summary: "test.user@gmail.com",
+  description: "",
+  updated: "2025-03-25T14:03:05.090Z",
+  timeZone: "America/Chicago",
+  accessRole: "owner",
+  defaultReminders: [
+    {
+      method: "popup",
+      minutes: 30,
+    },
+  ],
+  nextSyncToken: "CK_y7u-ypYwDEK_y7u-ypYwDGAUg24mI4AIo24mI4AI=",
+  items: [
+    {
+      kind: "calendar#event",
+      etag: '"3485822770180702"',
+      id: "72o12msae3t6au1lim41i8tu6j",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=NzJvMTJtc2FlM3Q2YXUxbGltNDFpOHR1NmpfMjAyNTAzMjRUMTUwMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-25T14:02:10.000Z",
+      updated: "2025-03-25T14:03:05.090Z",
+      summary: "🗣️ Meeting w/ team",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-03-24T10:00:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-24T11:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurrence: ["RRULE:FREQ=DAILY"],
+      iCalUID: "72o12msae3t6au1lim41i8tu6j@google.com",
+      sequence: 0,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+    {
+      kind: "calendar#event",
+      etag: '"3485822770180702"',
+      id: "72o12msae3t6au1lim41i8tu6j_20250328T150000Z",
+      status: "cancelled",
+      recurringEventId: "72o12msae3t6au1lim41i8tu6j",
+      originalStartTime: {
+        dateTime: "2025-03-28T10:00:00-05:00",
+        timeZone: "America/Chicago",
+      },
+    },
+  ],
+};
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/delete/single/delete.single.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/delete/single/delete.single.ts
new file mode 100644
index 00000000..c459df2c
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/delete/single/delete.single.ts
@@ -0,0 +1,65 @@
+import { gSchema$Events } from "@core/types/gcal";
+
+export const deleteSinglePayload: gSchema$Events = {
+  kind: "calendar#events",
+  etag: '"p327prdclkqioo0o"',
+  summary: "test.user@gmail.com",
+  description: "",
+  updated: "2025-03-25T13:06:14.176Z",
+  timeZone: "America/Chicago",
+  accessRole: "owner",
+  defaultReminders: [
+    {
+      method: "popup",
+      minutes: 30,
+    },
+  ],
+  nextSyncToken: "CI-dtZWmpYwDEI-dtZWmpYwDGAUg24mI4AIo24mI4AI=",
+  items: [
+    {
+      kind: "calendar#event",
+      etag: '"3485815948352798"',
+      id: "5hni4sj3ql1669otmjg7sn1mok",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=NWhuaTRzajNxbDE2NjlvdG1qZzdzbjFtb2tfMjAyNTAyMDlUMTQwMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-24T12:58:18.000Z",
+      updated: "2025-03-25T13:06:14.176Z",
+      summary: "🍜 Breakfast",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-02-09T08:00:00-06:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-02-09T09:00:00-06:00",
+        timeZone: "America/Chicago",
+      },
+      recurrence: ["RRULE:FREQ=DAILY;UNTIL=20250401T045959Z"],
+      iCalUID: "5hni4sj3ql1669otmjg7sn1mok@google.com",
+      sequence: 0,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+    {
+      kind: "calendar#event",
+      etag: '"3485815948352798"',
+      id: "5hni4sj3ql1669otmjg7sn1mok_20250325T130000Z",
+      status: "cancelled",
+      recurringEventId: "5hni4sj3ql1669otmjg7sn1mok",
+      originalStartTime: {
+        dateTime: "2025-03-25T08:00:00-05:00",
+        timeZone: "America/Chicago",
+      },
+    },
+  ],
+};
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/delete/single/index.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/delete/single/index.ts
new file mode 100644
index 00000000..8596578f
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/delete/single/index.ts
@@ -0,0 +1,7 @@
+import { deleteSinglePayload } from "./delete.single";
+import { deleteSinglePayload2 } from "./delete.single-2";
+
+export const deleteSingleEventPayloads = [
+  deleteSinglePayload,
+  deleteSinglePayload2,
+];
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/delete/this-and-following/delete.this-and-following-2.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/delete/this-and-following/delete.this-and-following-2.ts
new file mode 100644
index 00000000..2552769c
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/delete/this-and-following/delete.this-and-following-2.ts
@@ -0,0 +1,60 @@
+import { gSchema$Events } from "@core/types/gcal";
+
+export const deleteThisAndFollowingPayload2: gSchema$Events = {
+  kind: "calendar#events",
+  etag: '"p327u3v43meioo0o"',
+  summary: "test.user@gmail.com",
+  description: "",
+  updated: "2025-03-25T14:03:47.260Z",
+  timeZone: "America/Chicago",
+  accessRole: "owner",
+  defaultReminders: [
+    {
+      method: "popup",
+      minutes: 30,
+    },
+  ],
+  nextSyncToken: "CI_h_IOzpYwDEI_h_IOzpYwDGAUg24mI4AIo24mI4AI=",
+  items: [
+    {
+      kind: "calendar#event",
+      etag: '"3485822854521118"',
+      id: "72o12msae3t6au1lim41i8tu6j",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=NzJvMTJtc2FlM3Q2YXUxbGltNDFpOHR1NmpfMjAyNTAzMjRUMTUwMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-25T14:02:10.000Z",
+      updated: "2025-03-25T14:03:47.260Z",
+      summary: "🗣️ Meeting w/ team",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-03-24T10:00:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-24T11:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurrence: ["RRULE:FREQ=DAILY;UNTIL=20250326T045959Z"],
+      iCalUID: "72o12msae3t6au1lim41i8tu6j@google.com",
+      sequence: 0,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+    {
+      kind: "calendar#event",
+      etag: '"3485822854521118"',
+      id: "72o12msae3t6au1lim41i8tu6j_20250328T150000Z",
+      status: "cancelled",
+    },
+  ],
+};
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/delete/this-and-following/delete.this-and-following.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/delete/this-and-following/delete.this-and-following.ts
new file mode 100644
index 00000000..b45430da
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/delete/this-and-following/delete.this-and-following.ts
@@ -0,0 +1,65 @@
+import { gSchema$Events } from "@core/types/gcal";
+
+export const deleteThisAndFollowingPayload: gSchema$Events = {
+  kind: "calendar#events",
+  etag: '"p337tt2lekqioo0o"',
+  summary: "test.user@gmail.com",
+  description: "",
+  updated: "2025-03-25T13:07:05.909Z",
+  timeZone: "America/Chicago",
+  accessRole: "owner",
+  defaultReminders: [
+    {
+      method: "popup",
+      minutes: 30,
+    },
+  ],
+  nextSyncToken: "CM_eiq6mpYwDEM_eiq6mpYwDGAUg24mI4AIo24mI4AI=",
+  items: [
+    {
+      kind: "calendar#event",
+      etag: '"3485816051818142"',
+      id: "5hni4sj3ql1669otmjg7sn1mok",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=NWhuaTRzajNxbDE2NjlvdG1qZzdzbjFtb2tfMjAyNTAyMDlUMTQwMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-24T12:58:18.000Z",
+      updated: "2025-03-25T13:07:05.909Z",
+      summary: "🍜 Breakfast",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-02-09T08:00:00-06:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-02-09T09:00:00-06:00",
+        timeZone: "America/Chicago",
+      },
+      recurrence: ["RRULE:FREQ=DAILY;UNTIL=20250327T045959Z"],
+      iCalUID: "5hni4sj3ql1669otmjg7sn1mok@google.com",
+      sequence: 0,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+    {
+      kind: "calendar#event",
+      etag: '"3485816051818142"',
+      id: "5hni4sj3ql1669otmjg7sn1mok_20250325T130000Z",
+      status: "cancelled",
+      recurringEventId: "5hni4sj3ql1669otmjg7sn1mok",
+      originalStartTime: {
+        dateTime: "2025-03-25T08:00:00-05:00",
+        timeZone: "America/Chicago",
+      },
+    },
+  ],
+};
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/delete/this-and-following/index.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/delete/this-and-following/index.ts
new file mode 100644
index 00000000..7898b6db
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/delete/this-and-following/index.ts
@@ -0,0 +1,7 @@
+import { deleteThisAndFollowingPayload } from "./delete.this-and-following";
+import { deleteThisAndFollowingPayload2 } from "./delete.this-and-following-2";
+
+export const deleteThisAndFollowingPayloads = [
+  deleteThisAndFollowingPayload,
+  deleteThisAndFollowingPayload2,
+];
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/all/all-1.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/all/all-1.ts
new file mode 100644
index 00000000..93abd96a
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/all/all-1.ts
@@ -0,0 +1,129 @@
+import { gSchema$Events } from "@core/types/gcal";
+
+export const allPayload1: gSchema$Events = {
+  kind: "calendar#events",
+  etag: '"p32nuv4dugig8o0o"',
+  summary: "test.user@gmail.com",
+  description: "",
+  updated: "2025-03-23T10:52:14.082Z",
+  timeZone: "America/Chicago",
+  accessRole: "owner",
+  defaultReminders: [
+    {
+      method: "popup",
+      minutes: 30,
+    },
+  ],
+  nextSyncToken: "CK_vkb6EoIwDEK_vkb6EoIwDGAUg79XP3wIo79XP3wI=",
+  items: [
+    {
+      kind: "calendar#event",
+      etag: '"3485454268165982"',
+      id: "68k0p6ackplecqs9fuvbs1fju0",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=NjhrMHA2YWNrcGxlY3FzOWZ1dmJzMWZqdTBfMjAyNTAzMjRUMTIzMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-23T10:41:35.000Z",
+      updated: "2025-03-23T10:52:14.082Z",
+      summary: "🧽 Dishes",
+      description: "Changed all occurrences",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-03-24T07:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-24T08:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurrence: ["RRULE:FREQ=DAILY;UNTIL=20250326T045959Z"],
+      iCalUID: "68k0p6ackplecqs9fuvbs1fju0@google.com",
+      sequence: 0,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+    {
+      kind: "calendar#event",
+      etag: '"3485454268165982"',
+      id: "68k0p6ackplecqs9fuvbs1fju0_20250324T123000Z",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=NjhrMHA2YWNrcGxlY3FzOWZ1dmJzMWZqdTBfMjAyNTAzMjRUMTIzMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-23T10:41:35.000Z",
+      updated: "2025-03-23T10:52:14.082Z",
+      summary: "🧽 Dishes",
+      description: "Changed all occurrences",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-03-24T07:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-24T08:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurringEventId: "68k0p6ackplecqs9fuvbs1fju0",
+      originalStartTime: {
+        dateTime: "2025-03-24T07:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      iCalUID: "68k0p6ackplecqs9fuvbs1fju0@google.com",
+      sequence: 0,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+    {
+      kind: "calendar#event",
+      etag: '"3485454268165982"',
+      id: "68k0p6ackplecqs9fuvbs1fju0_R20250326T123000",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=NjhrMHA2YWNrcGxlY3FzOWZ1dmJzMWZqdTBfMjAyNTAzMjZUMTIzMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-23T10:41:35.000Z",
+      updated: "2025-03-23T10:52:14.082Z",
+      summary: "🧽 Dishes",
+      description: "Changed all occurrences",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-03-26T07:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-26T08:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurrence: ["RRULE:FREQ=DAILY"],
+      iCalUID: "68k0p6ackplecqs9fuvbs1fju0_R20250326T123000@google.com",
+      sequence: 0,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+  ],
+};
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/all/all-2.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/all/all-2.ts
new file mode 100644
index 00000000..48de40cd
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/all/all-2.ts
@@ -0,0 +1,132 @@
+import { gSchema$Events } from "@core/types/gcal";
+
+export const allPayload2: gSchema$Events = {
+  kind: "calendar#events",
+  etag: '"p327pdad8ieg8o0o"',
+  summary: "test.user@gmail.com",
+  description: "",
+  updated: "2025-03-23T11:58:34.859Z",
+  timeZone: "America/Chicago",
+  accessRole: "owner",
+  defaultReminders: [
+    {
+      method: "popup",
+      minutes: 30,
+    },
+  ],
+  nextSyncToken: "CI-WqaiToIwDEI-WqaiToIwDGAUg79XP3wIo79XP3wI=",
+  items: [
+    {
+      kind: "calendar#event",
+      etag: '"3485462229718558"',
+      id: "68k0p6ackplecqs9fuvbs1fju0",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=NjhrMHA2YWNrcGxlY3FzOWZ1dmJzMWZqdTBfMjAyNTAzMjRUMTIzMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-23T10:41:35.000Z",
+      updated: "2025-03-23T11:58:34.859Z",
+      summary: "🧽 Dishes",
+      description:
+        "Originally was: 🍽️\nThen clicked the fourth instances, renamed to 🧽, and applied to ALL",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-03-24T07:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-24T08:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurrence: ["RRULE:FREQ=DAILY;UNTIL=20250326T045959Z"],
+      iCalUID: "68k0p6ackplecqs9fuvbs1fju0@google.com",
+      sequence: 0,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+    {
+      kind: "calendar#event",
+      etag: '"3485462229718558"',
+      id: "68k0p6ackplecqs9fuvbs1fju0_20250324T123000Z",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=NjhrMHA2YWNrcGxlY3FzOWZ1dmJzMWZqdTBfMjAyNTAzMjRUMTIzMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-23T10:41:35.000Z",
+      updated: "2025-03-23T11:58:34.859Z",
+      summary: "🧽 Dishes",
+      description:
+        "Originally was: 🍽️\nThen clicked the fourth instances, renamed to 🧽, and applied to ALL",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-03-24T07:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-24T08:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurringEventId: "68k0p6ackplecqs9fuvbs1fju0",
+      originalStartTime: {
+        dateTime: "2025-03-24T07:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      iCalUID: "68k0p6ackplecqs9fuvbs1fju0@google.com",
+      sequence: 0,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+    {
+      kind: "calendar#event",
+      etag: '"3485462229718558"',
+      id: "68k0p6ackplecqs9fuvbs1fju0_R20250326T123000",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=NjhrMHA2YWNrcGxlY3FzOWZ1dmJzMWZqdTBfMjAyNTAzMjZUMTIzMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-23T10:41:35.000Z",
+      updated: "2025-03-23T11:58:34.859Z",
+      summary: "🧽 Dishes",
+      description:
+        "Originally was: 🍽️\nThen clicked the fourth instances, renamed to 🧽, and applied to ALL",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-03-26T07:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-26T08:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurrence: ["RRULE:FREQ=DAILY"],
+      iCalUID: "68k0p6ackplecqs9fuvbs1fju0_R20250326T123000@google.com",
+      sequence: 0,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+  ],
+};
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/all/index.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/all/index.ts
new file mode 100644
index 00000000..a1e9059b
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/all/index.ts
@@ -0,0 +1,4 @@
+import { allPayload1 } from "./all-1";
+import { allPayload2 } from "./all-2";
+
+export const editAllPayloads = [allPayload1, allPayload2];
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/single/index.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/single/index.ts
new file mode 100644
index 00000000..ff87ef15
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/single/index.ts
@@ -0,0 +1,11 @@
+import { singleInstance1Payload } from "./single.instance-1";
+import { singleInstance2Payload } from "./single.instance-2";
+import { singleInstance3Payload } from "./single.instance-3";
+import { singleInstance4Payload } from "./single.instance-4";
+
+export const editSingleEventPayloads = [
+  singleInstance1Payload,
+  singleInstance2Payload,
+  singleInstance3Payload,
+  singleInstance4Payload,
+];
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/single/single.instance-1.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/single/single.instance-1.ts
new file mode 100644
index 00000000..48eb5f1c
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/single/single.instance-1.ts
@@ -0,0 +1,92 @@
+import { gSchema$Events } from "@core/types/gcal";
+
+export const singleInstance1Payload: gSchema$Events = {
+  kind: "calendar#events",
+  etag: '"p32fttgkdgag8o0o"',
+  summary: "test.user@gmail.com",
+  description: "",
+  updated: "2025-03-23T10:41:35.252Z",
+  timeZone: "America/Chicago",
+  accessRole: "owner",
+  defaultReminders: [
+    {
+      method: "popup",
+      minutes: 30,
+    },
+  ],
+  nextSyncToken: "CJ_ewo2CoIwDEJ_ewo2CoIwDGAUg79XP3wIo79XP3wI=",
+  items: [
+    {
+      kind: "calendar#event",
+      etag: '"3485452990504510"',
+      id: "68k0p6ackplecqs9fuvbs1fju0",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=NjhrMHA2YWNrcGxlY3FzOWZ1dmJzMWZqdTBfMjAyNTAzMjRUMTIzMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-23T10:41:35.000Z",
+      updated: "2025-03-23T10:41:35.252Z",
+      summary: "🍽️ Dishes",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-03-24T07:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-24T08:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurrence: ["RRULE:FREQ=DAILY"],
+      iCalUID: "68k0p6ackplecqs9fuvbs1fju0@google.com",
+      sequence: 0,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+    {
+      kind: "calendar#event",
+      etag: '"3485452990504511"',
+      id: "68k0p6ackplecqs9fuvbs1fju0_20250324T123000Z",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=NjhrMHA2YWNrcGxlY3FzOWZ1dmJzMWZqdTBfMjAyNTAzMjRUMTIzMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-23T10:41:35.000Z",
+      updated: "2025-03-23T10:41:35.252Z",
+      summary: "🍽️ Dishes (Modified)",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-03-24T12:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-24T13:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurringEventId: "68k0p6ackplecqs9fuvbs1fju0",
+      originalStartTime: {
+        dateTime: "2025-03-24T07:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      iCalUID: "68k0p6ackplecqs9fuvbs1fju0@google.com",
+      sequence: 1,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+  ],
+};
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/single/single.instance-2.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/single/single.instance-2.ts
new file mode 100644
index 00000000..a2a54ea5
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/single/single.instance-2.ts
@@ -0,0 +1,93 @@
+import { gSchema$Events } from "@core/types/gcal";
+
+export const singleInstance2Payload: gSchema$Events = {
+  kind: "calendar#events",
+  etag: '"p33fobpn8jig8o0o"',
+  summary: "test.user@gmail.com",
+  description: "",
+  updated: "2025-03-23T12:41:05.993Z",
+  timeZone: "America/Chicago",
+  accessRole: "owner",
+  defaultReminders: [
+    {
+      method: "popup",
+      minutes: 30,
+    },
+  ],
+  nextSyncToken: "CN-F5uicoIwDEN-F5uicoIwDGAUg79XP3wIo79XP3wI=",
+  items: [
+    {
+      kind: "calendar#event",
+      etag: '"3485467331986878"',
+      id: "0e6062d5un60i5sn2m9et69c27",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=MGU2MDYyZDV1bjYwaTVzbjJtOWV0NjljMjdfMjAyNTAzMjRUMTIzMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-23T12:39:52.000Z",
+      updated: "2025-03-23T12:41:05.993Z",
+      summary: "🍽️ Dishes",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-03-24T07:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-24T08:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurrence: ["RRULE:FREQ=DAILY"],
+      iCalUID: "0e6062d5un60i5sn2m9et69c27@google.com",
+      sequence: 0,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+    {
+      kind: "calendar#event",
+      etag: '"3485467331986878"',
+      id: "0e6062d5un60i5sn2m9et69c27_20250326T123000Z",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=MGU2MDYyZDV1bjYwaTVzbjJtOWV0NjljMjdfMjAyNTAzMjZUMTIzMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-23T12:39:52.000Z",
+      updated: "2025-03-23T12:41:05.993Z",
+      summary: "🌙 Dishes-Edited One",
+      description: "changed time from morning to night",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-03-26T20:45:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-26T21:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurringEventId: "0e6062d5un60i5sn2m9et69c27",
+      originalStartTime: {
+        dateTime: "2025-03-26T07:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      iCalUID: "0e6062d5un60i5sn2m9et69c27@google.com",
+      sequence: 1,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+  ],
+};
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/single/single.instance-3.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/single/single.instance-3.ts
new file mode 100644
index 00000000..d2d398e9
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/single/single.instance-3.ts
@@ -0,0 +1,171 @@
+import { gSchema$Events } from "@core/types/gcal";
+
+/**
+ * Payload with instances that were each edited individually
+ */
+export const singleInstance3Payload: gSchema$Events = {
+  kind: "calendar#events",
+  etag: '"p32fv7hlkjag8o0o"',
+  summary: "test.user@gmail.com",
+  description: "",
+  updated: "2025-03-23T12:30:19.560Z",
+  timeZone: "America/Chicago",
+  accessRole: "owner",
+  defaultReminders: [
+    {
+      method: "popup",
+      minutes: 30,
+    },
+  ],
+  nextSyncToken: "CJ_zxrSaoIwDEJ_zxrSaoIwDGAUg79XP3wIo79XP3wI=",
+  items: [
+    {
+      kind: "calendar#event",
+      etag: '"3485466039120702"',
+      id: "1kmd7abo2uok36n1pkaemqncba",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=MWttZDdhYm8ydW9rMzZuMXBrYWVtcW5jYmFfMjAyNTAzMjRUMTIzMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-23T12:27:05.000Z",
+      updated: "2025-03-23T12:30:19.560Z",
+      summary: "🍽️ Dishes",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-03-24T07:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-24T08:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurrence: ["RRULE:FREQ=DAILY"],
+      iCalUID: "1kmd7abo2uok36n1pkaemqncba@google.com",
+      sequence: 0,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+    {
+      kind: "calendar#event",
+      etag: '"3485466039120702"',
+      id: "1kmd7abo2uok36n1pkaemqncba_20250326T123000Z",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=MWttZDdhYm8ydW9rMzZuMXBrYWVtcW5jYmFfMjAyNTAzMjZUMTIzMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-23T12:27:05.000Z",
+      updated: "2025-03-23T12:30:19.560Z",
+      summary: "🧼 Dishes-Edited Just This One",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-03-26T07:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-26T08:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurringEventId: "1kmd7abo2uok36n1pkaemqncba",
+      originalStartTime: {
+        dateTime: "2025-03-26T07:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      iCalUID: "1kmd7abo2uok36n1pkaemqncba@google.com",
+      sequence: 0,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+    {
+      kind: "calendar#event",
+      etag: '"3485466039120702"',
+      id: "1kmd7abo2uok36n1pkaemqncba_20250327T123000Z",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=MWttZDdhYm8ydW9rMzZuMXBrYWVtcW5jYmFfMjAyNTAzMjdUMTIzMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-23T12:27:05.000Z",
+      updated: "2025-03-23T12:30:19.560Z",
+      summary: "🫧 Dishes - Edited just this one",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-03-27T07:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-27T08:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurringEventId: "1kmd7abo2uok36n1pkaemqncba",
+      originalStartTime: {
+        dateTime: "2025-03-27T07:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      iCalUID: "1kmd7abo2uok36n1pkaemqncba@google.com",
+      sequence: 0,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+    {
+      kind: "calendar#event",
+      etag: '"3485466039120702"',
+      id: "1kmd7abo2uok36n1pkaemqncba_20250328T123000Z",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=MWttZDdhYm8ydW9rMzZuMXBrYWVtcW5jYmFfMjAyNTAzMjhUMTIzMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-23T12:27:05.000Z",
+      updated: "2025-03-23T12:30:19.560Z",
+      summary: "🧺 Dishes - Just this one",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-03-28T07:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-28T08:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurringEventId: "1kmd7abo2uok36n1pkaemqncba",
+      originalStartTime: {
+        dateTime: "2025-03-28T07:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      iCalUID: "1kmd7abo2uok36n1pkaemqncba@google.com",
+      sequence: 0,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+  ],
+};
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/single/single.instance-4.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/single/single.instance-4.ts
new file mode 100644
index 00000000..3c50480b
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/single/single.instance-4.ts
@@ -0,0 +1,54 @@
+import { gSchema$Events } from "@core/types/gcal";
+
+export const singleInstance4Payload: gSchema$Events = {
+  kind: "calendar#events",
+  etag: '"p32fv7hlkjag8o0o"',
+  summary: "test.user@gmail.com",
+  description: "",
+  updated: "2025-03-23T12:30:19.560Z",
+  timeZone: "America/Chicago",
+  accessRole: "owner",
+  defaultReminders: [
+    {
+      method: "popup",
+      minutes: 30,
+    },
+  ],
+  nextSyncToken: "CJ_zxrSaoIwDEJ_zxrSaoIwDGAUg79XP3wIo79XP3wI=",
+  items: [
+    {
+      kind: "calendar#event",
+      etag: '"3486841886526878"',
+      id: "e5srrkr361upjc2be22u6ti4pe_20250401T120000Z",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=ZTVzcnJrcjM2MXVwamMyYmUyMnU2dGk0cGVfMjAyNTA0MDFUMTIwMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-24T12:58:18.000Z",
+      updated: "2025-03-31T11:35:43.263Z",
+      summary: "🍊 Breakfast: This And Following v2",
+      description:
+        "Was originally 8-9am and a hot dish emoji. \n" +
+        "Changed to avacado and 7-8am\n" +
+        "Then changed to orange",
+      creator: { email: "test.user@gmail.com", self: true },
+      organizer: { email: "test.user@gmail.com", self: true },
+      start: {
+        dateTime: "2025-03-30T07:00:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-30T08:00:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurringEventId: "e5srrkr361upjc2be22u6ti4pe",
+      originalStartTime: {
+        dateTime: "2025-04-01T07:00:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      iCalUID: "e5srrkr361upjc2be22u6ti4pe@google.com",
+      sequence: 3,
+      reminders: { useDefault: true },
+      eventType: "default",
+    },
+  ],
+};
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/batch1/index.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/batch1/index.ts
new file mode 100644
index 00000000..adc044b6
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/batch1/index.ts
@@ -0,0 +1,9 @@
+import { thisAndFollowing0Payload } from "./this-and-following-v2-0";
+import { thisAndFollowing1Payload } from "./this-and-following-v2-1";
+import { thisAndFollowing2Payload } from "./this-and-following-v2-2";
+
+export const thisAndFollowingBatch1Payloads = [
+  thisAndFollowing0Payload,
+  thisAndFollowing1Payload,
+  thisAndFollowing2Payload,
+];
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/batch1/this-and-following-v2-0.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/batch1/this-and-following-v2-0.ts
new file mode 100644
index 00000000..6e5bc1db
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/batch1/this-and-following-v2-0.ts
@@ -0,0 +1,90 @@
+import { gSchema$Events } from "@core/types/gcal";
+
+export const thisAndFollowing0Payload: gSchema$Events = {
+  kind: "calendar#events",
+  etag: '"p32vujd53seh8o0o"',
+  summary: "test.user@gmail.com",
+  description: "",
+  updated: "2025-03-24T13:01:38.877Z",
+  timeZone: "America/Chicago",
+  accessRole: "owner",
+  defaultReminders: [
+    {
+      method: "popup",
+      minutes: 30,
+    },
+  ],
+  nextSyncToken: "CL_ptKPjoowDEL_ptKPjoowDGAUg24mI4AIo24mI4AI=",
+  items: [
+    {
+      kind: "calendar#event",
+      etag: '"3485642597755262"',
+      id: "5hni4sj3ql1669otmjg7sn1mok",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=NWhuaTRzajNxbDE2NjlvdG1qZzdzbjFtb2tfMjAyNTAyMDlUMTQwMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-24T12:58:18.000Z",
+      updated: "2025-03-24T13:01:38.877Z",
+      summary: "🍜 Breakfast",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-02-09T08:00:00-06:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-02-09T09:00:00-06:00",
+        timeZone: "America/Chicago",
+      },
+      recurrence: ["RRULE:FREQ=DAILY;UNTIL=20250401T045959Z"],
+      iCalUID: "5hni4sj3ql1669otmjg7sn1mok@google.com",
+      sequence: 0,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+    {
+      kind: "calendar#event",
+      etag: '"3485642597755262"',
+      id: "e5srrkr361upjc2be22u6ti4pe",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=ZTVzcnJrcjM2MXVwamMyYmUyMnU2dGk0cGVfMjAyNTA0MDFUMTIwMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-24T12:58:18.000Z",
+      updated: "2025-03-24T13:01:38.877Z",
+      summary: "🥑 Breakfast: This And Following",
+      description:
+        "Was originally 8-9am and a hot dish emoji. \nChanged to avacado and 7-8am\nThe event I changed the recurrence on was April 1, 2025\n----\nAll events before April 1 should have hot dish",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-04-01T07:00:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-04-01T08:00:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurrence: ["RRULE:FREQ=DAILY"],
+      iCalUID: "e5srrkr361upjc2be22u6ti4pe@google.com",
+      sequence: 1,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+  ],
+};
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/batch1/this-and-following-v2-1.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/batch1/this-and-following-v2-1.ts
new file mode 100644
index 00000000..4530e29d
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/batch1/this-and-following-v2-1.ts
@@ -0,0 +1,95 @@
+import { gSchema$Events } from "@core/types/gcal";
+
+/**
+ * Scenario: After editing a recurrence that was previously edited
+ */
+export const thisAndFollowing1Payload: gSchema$Events = {
+  kind: "calendar#events",
+  etag: '"p33vrvivsseh8o0o"',
+  summary: "test.user@gmail.com",
+  description: "",
+  updated: "2025-03-24T13:04:45.895Z",
+  timeZone: "America/Chicago",
+  accessRole: "owner",
+  defaultReminders: [
+    {
+      method: "popup",
+      minutes: 30,
+    },
+  ],
+  nextSyncToken: "CP-_y_zjoowDEP-_y_zjoowDGAUg24mI4AIo24mI4AI=",
+  items: [
+    {
+      kind: "calendar#event",
+      etag: '"3485642971791358"',
+      id: "e5srrkr361upjc2be22u6ti4pe",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=ZTVzcnJrcjM2MXVwamMyYmUyMnU2dGk0cGVfMjAyNTA0MDFUMTIwMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-24T12:58:18.000Z",
+      updated: "2025-03-24T13:04:45.895Z",
+      summary: "🥑 Breakfast: This And Following",
+      description:
+        "Was originally 8-9am and a hot dish emoji. \nChanged to avacado and 7-8am\nThe event I changed the recurrence on was April 1, 2025\n----\nAll events before April 1 should have hot dish",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-04-01T07:00:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-04-01T08:00:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurrence: ["RRULE:FREQ=DAILY;UNTIL=20250409T045959Z"],
+      iCalUID: "e5srrkr361upjc2be22u6ti4pe@google.com",
+      sequence: 1,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+    {
+      kind: "calendar#event",
+      etag: '"3485642971791358"',
+      id: "e5srrkr361upjc2be22u6ti4pe_R20250409T120000",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=ZTVzcnJrcjM2MXVwamMyYmUyMnU2dGk0cGVfMjAyNTA0MDlUMTIwMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-24T12:58:18.000Z",
+      updated: "2025-03-24T13:04:45.895Z",
+      summary: "🍊 Breakfast: This And Following v2",
+      description:
+        "Was originally 8-9am and a hot dish emoji. \nChanged to avacado and 7-8am\nThen changed to orange",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-04-09T07:00:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-04-09T08:00:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurrence: ["RRULE:FREQ=DAILY"],
+      iCalUID: "e5srrkr361upjc2be22u6ti4pe_R20250409T120000@google.com",
+      sequence: 1,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+  ],
+};
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/batch1/this-and-following-v2-2.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/batch1/this-and-following-v2-2.ts
new file mode 100644
index 00000000..8a2c361c
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/batch1/this-and-following-v2-2.ts
@@ -0,0 +1,88 @@
+import { gSchema$Events } from "@core/types/gcal";
+
+export const thisAndFollowing2Payload: gSchema$Events = {
+  kind: "calendar#events",
+  etag: '"p327plvtbvatoo0o"',
+  summary: "test.user@gmail.com",
+  description: "",
+  updated: "2025-04-03T13:21:24.348Z",
+  timeZone: "America/Chicago",
+  accessRole: "owner",
+  defaultReminders: [
+    {
+      method: "popup",
+      minutes: 30,
+    },
+  ],
+  nextSyncToken: "CI-a_6v6u4wDEI-a_6v6u4wDGAUgxqid4QIoxqid4QI=",
+  items: [
+    {
+      kind: "calendar#event",
+      etag: '"3487372968696350"',
+      id: "3a8nkmjcuj9a6f4qn917se9fv3",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=M2E4bmttamN1ajlhNmY0cW45MTdzZTlmdjNfMjAyNTA0MDJUMTk0NTAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-04-03T13:21:06.000Z",
+      updated: "2025-04-03T13:21:24.348Z",
+      summary: "r5",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-04-02T14:45:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-04-02T15:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurrence: ["RRULE:FREQ=DAILY;UNTIL=20250403T045959Z"],
+      iCalUID: "3a8nkmjcuj9a6f4qn917se9fv3@google.com",
+      sequence: 0,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+    {
+      kind: "calendar#event",
+      etag: '"3487372968696350"',
+      id: "3a8nkmjcuj9a6f4qn917se9fv3_R20250403T194500",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=M2E4bmttamN1ajlhNmY0cW45MTdzZTlmdjNfMjAyNTA0MDNUMTk0NTAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-04-03T13:21:06.000Z",
+      updated: "2025-04-03T13:21:24.348Z",
+      summary: "r5-i",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-04-03T14:45:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-04-03T15:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurrence: ["RRULE:FREQ=DAILY"],
+      iCalUID: "3a8nkmjcuj9a6f4qn917se9fv3_R20250403T194500@google.com",
+      sequence: 0,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+  ],
+};
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/batch2/index.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/batch2/index.ts
new file mode 100644
index 00000000..60a70568
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/batch2/index.ts
@@ -0,0 +1,11 @@
+import { thisAndFollowing1Payload } from "./this-and-following-1";
+import { thisAndFollowing2Payload } from "./this-and-following-2";
+import { thisAndFollowing3Payload } from "./this-and-following-3";
+import { thisAndFollowing4Payload } from "./this-and-following-4";
+
+export const thisAndFollowingBatch2Payloads = [
+  thisAndFollowing1Payload,
+  thisAndFollowing2Payload,
+  thisAndFollowing3Payload,
+  thisAndFollowing4Payload,
+];
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/batch2/this-and-following-1.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/batch2/this-and-following-1.ts
new file mode 100644
index 00000000..803ff44e
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/batch2/this-and-following-1.ts
@@ -0,0 +1,90 @@
+import { gSchema$Events } from "@core/types/gcal";
+
+export const thisAndFollowing1Payload: gSchema$Events = {
+  kind: "calendar#events",
+  etag: '"p33npng78iug8o0o"',
+  summary: "test.user@gmail.com",
+  description: "",
+  updated: "2025-03-23T12:18:43.196Z",
+  timeZone: "America/Chicago",
+  accessRole: "owner",
+  defaultReminders: [
+    {
+      method: "popup",
+      minutes: 30,
+    },
+  ],
+  nextSyncToken: "CO-bwOiXoIwDEO-bwOiXoIwDGAUg79XP3wIo79XP3wI=",
+  items: [
+    {
+      kind: "calendar#event",
+      etag: '"3485464646392798"',
+      id: "4k3h1bqn0pmn2qmvc7m0b6ip2q",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=NGszaDFicW4wcG1uMnFtdmM3bTBiNmlwMnFfMjAyNTAzMjRUMTIzMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-23T12:16:13.000Z",
+      updated: "2025-03-23T12:18:43.196Z",
+      summary: "🍽️ Dishes",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-03-24T07:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-24T08:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurrence: ["RRULE:FREQ=DAILY;UNTIL=20250327T045959Z"],
+      iCalUID: "4k3h1bqn0pmn2qmvc7m0b6ip2q@google.com",
+      sequence: 0,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+    {
+      kind: "calendar#event",
+      etag: '"3485464646392798"',
+      id: "4k3h1bqn0pmn2qmvc7m0b6ip2q_R20250327T123000",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=NGszaDFicW4wcG1uMnFtdmM3bTBiNmlwMnFfMjAyNTAzMjdUMTIzMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-23T12:16:13.000Z",
+      updated: "2025-03-23T12:18:43.196Z",
+      summary: "🧽 Dishes",
+      description:
+        "Originally was: 🍽️\nThen clicked the fourth instances, renamed to 🧽, and applied to THIS AND FOLLOWING",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-03-27T07:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-27T08:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurrence: ["RRULE:FREQ=DAILY"],
+      iCalUID: "4k3h1bqn0pmn2qmvc7m0b6ip2q_R20250327T123000@google.com",
+      sequence: 0,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+  ],
+};
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/batch2/this-and-following-2.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/batch2/this-and-following-2.ts
new file mode 100644
index 00000000..cf7c43c9
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/batch2/this-and-following-2.ts
@@ -0,0 +1,164 @@
+import { gSchema$Events } from "@core/types/gcal";
+
+export const thisAndFollowing2Payload: gSchema$Events = {
+  kind: "calendar#events",
+  etag: '"p337pvgsdpqh8o0o"',
+  summary: "test.user@gmail.com",
+  description: "",
+  updated: "2025-03-24T11:26:55.832Z",
+  timeZone: "America/Chicago",
+  accessRole: "owner",
+  defaultReminders: [
+    {
+      method: "popup",
+      minutes: 30,
+    },
+  ],
+  nextSyncToken: "CM-fw43OoowDEM-fw43OoowDGAUg24mI4AIo24mI4AI=",
+  items: [
+    {
+      kind: "calendar#event",
+      etag: '"3485631231664030"',
+      id: "3i234gl45i6i1s8rpui7dleor0",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=M2kyMzRnbDQ1aTZpMXM4cnB1aTdkbGVvcjBfMjAyNTAzMTJUMTMxNTAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-12T12:18:30.000Z",
+      updated: "2025-03-24T11:26:55.832Z",
+      summary: "📝 journal",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-03-12T08:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-12T09:00:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurrence: ["RRULE:FREQ=DAILY;UNTIL=20250326T045959Z"],
+      iCalUID: "3i234gl45i6i1s8rpui7dleor0@google.com",
+      sequence: 0,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+    {
+      kind: "calendar#event",
+      etag: '"3485631231664030"',
+      id: "3i234gl45i6i1s8rpui7dleor0_20250315T131500Z",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=M2kyMzRnbDQ1aTZpMXM4cnB1aTdkbGVvcjBfMjAyNTAzMTVUMTMxNTAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-12T12:18:30.000Z",
+      updated: "2025-03-24T11:26:55.832Z",
+      summary: "📝 journal",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-03-15T16:00:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-15T16:45:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurringEventId: "3i234gl45i6i1s8rpui7dleor0",
+      originalStartTime: {
+        dateTime: "2025-03-15T08:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      iCalUID: "3i234gl45i6i1s8rpui7dleor0@google.com",
+      sequence: 1,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+    {
+      kind: "calendar#event",
+      etag: '"3485631231664030"',
+      id: "3i234gl45i6i1s8rpui7dleor0_20250319T131500Z",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=M2kyMzRnbDQ1aTZpMXM4cnB1aTdkbGVvcjBfMjAyNTAzMTlUMTMxNTAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-12T12:18:30.000Z",
+      updated: "2025-03-24T11:26:55.832Z",
+      summary: "📝 journal",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-03-20T16:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-20T17:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurringEventId: "3i234gl45i6i1s8rpui7dleor0",
+      originalStartTime: {
+        dateTime: "2025-03-19T08:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      iCalUID: "3i234gl45i6i1s8rpui7dleor0@google.com",
+      sequence: 1,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+    {
+      kind: "calendar#event",
+      etag: '"3485631231664030"',
+      id: "3i234gl45i6i1s8rpui7dleor0_R20250326T131500",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=M2kyMzRnbDQ1aTZpMXM4cnB1aTdkbGVvcjBfMjAyNTAzMjZUMTMxNTAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-12T12:18:30.000Z",
+      updated: "2025-03-24T11:26:55.832Z",
+      summary: "🐛 Journal - This And Following",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-03-26T08:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-26T09:00:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurrence: ["RRULE:FREQ=DAILY"],
+      iCalUID: "3i234gl45i6i1s8rpui7dleor0_R20250326T131500@google.com",
+      sequence: 0,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+  ],
+};
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/batch2/this-and-following-3.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/batch2/this-and-following-3.ts
new file mode 100644
index 00000000..50e7d709
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/batch2/this-and-following-3.ts
@@ -0,0 +1,88 @@
+import { gSchema$Events } from "@core/types/gcal";
+
+export const thisAndFollowing3Payload: gSchema$Events = {
+  kind: "calendar#events",
+  etag: '"p32vp9duvpqh8o0o"',
+  summary: "test.user@gmail.com",
+  description: "",
+  updated: "2025-03-24T11:29:47.600Z",
+  timeZone: "America/Chicago",
+  accessRole: "owner",
+  defaultReminders: [
+    {
+      method: "popup",
+      minutes: 30,
+    },
+  ],
+  nextSyncToken: "CL-Ut9_OoowDEL-Ut9_OoowDGAUg24mI4AIo24mI4AI=",
+  items: [
+    {
+      kind: "calendar#event",
+      etag: '"3485631575200894"',
+      id: "0214krqh7jr2n0bobv19djs5aj",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=MDIxNGtycWg3anIybjBib2J2MTlkanM1YWpfMjAyNTAzMTJUMTQxNTAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-12T11:50:21.000Z",
+      updated: "2025-03-24T11:29:47.600Z",
+      summary: "🥓 Breakfast",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-03-12T09:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-12T10:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurrence: ["RRULE:FREQ=DAILY;UNTIL=20250326T045959Z"],
+      iCalUID: "0214krqh7jr2n0bobv19djs5aj@google.com",
+      sequence: 1,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+    {
+      kind: "calendar#event",
+      etag: '"3485631575200894"',
+      id: "0214krqh7jr2n0bobv19djs5aj_R20250326T141500",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=MDIxNGtycWg3anIybjBib2J2MTlkanM1YWpfMjAyNTAzMjZUMTQxNTAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-12T11:50:21.000Z",
+      updated: "2025-03-24T11:29:47.600Z",
+      summary: "🍳 Breakfast - This And Following",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-03-26T09:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-26T10:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurrence: ["RRULE:FREQ=DAILY"],
+      iCalUID: "0214krqh7jr2n0bobv19djs5aj_R20250326T141500@google.com",
+      sequence: 1,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+  ],
+};
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/batch2/this-and-following-4.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/batch2/this-and-following-4.ts
new file mode 100644
index 00000000..9adb21e8
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/batch2/this-and-following-4.ts
@@ -0,0 +1,128 @@
+import { gSchema$Events } from "@core/types/gcal";
+
+export const thisAndFollowing4Payload: gSchema$Events = {
+  kind: "calendar#events",
+  etag: '"p337rddesgeg8o0o"',
+  summary: "test.user@gmail.com",
+  description: "",
+  updated: "2025-03-23T10:48:49.144Z",
+  timeZone: "America/Chicago",
+  accessRole: "owner",
+  defaultReminders: [
+    {
+      method: "popup",
+      minutes: 30,
+    },
+  ],
+  nextSyncToken: "CM-2tdyDoIwDEM-2tdyDoIwDGAUg79XP3wIo79XP3wI=",
+  items: [
+    {
+      kind: "calendar#event",
+      etag: '"3485453858289310"',
+      id: "68k0p6ackplecqs9fuvbs1fju0",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=NjhrMHA2YWNrcGxlY3FzOWZ1dmJzMWZqdTBfMjAyNTAzMjRUMTIzMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-23T10:41:35.000Z",
+      updated: "2025-03-23T10:48:49.144Z",
+      summary: "🍽️ Dishes",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-03-24T07:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-24T08:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurrence: ["RRULE:FREQ=DAILY;UNTIL=20250326T045959Z"],
+      iCalUID: "68k0p6ackplecqs9fuvbs1fju0@google.com",
+      sequence: 0,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+    {
+      kind: "calendar#event",
+      etag: '"3485453858289310"',
+      id: "68k0p6ackplecqs9fuvbs1fju0_20250324T123000Z",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=NjhrMHA2YWNrcGxlY3FzOWZ1dmJzMWZqdTBfMjAyNTAzMjRUMTIzMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-23T10:41:35.000Z",
+      updated: "2025-03-23T10:48:49.144Z",
+      summary: "🍽️ Dishes: Edited Just This One",
+      description: "Only the title and description were changed",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-03-24T07:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-24T08:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurringEventId: "68k0p6ackplecqs9fuvbs1fju0",
+      originalStartTime: {
+        dateTime: "2025-03-24T07:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      iCalUID: "68k0p6ackplecqs9fuvbs1fju0@google.com",
+      sequence: 0,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+    {
+      kind: "calendar#event",
+      etag: '"3485453858289310"',
+      id: "68k0p6ackplecqs9fuvbs1fju0_R20250326T123000",
+      status: "confirmed",
+      htmlLink:
+        "https://www.google.com/calendar/event?eid=NjhrMHA2YWNrcGxlY3FzOWZ1dmJzMWZqdTBfMjAyNTAzMjZUMTIzMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+      created: "2025-03-23T10:41:35.000Z",
+      updated: "2025-03-23T10:48:49.144Z",
+      summary: "🍽️ Dishes 🔮",
+      description: "Selected to change this and future events",
+      creator: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      organizer: {
+        email: "test.user@gmail.com",
+        self: true,
+      },
+      start: {
+        dateTime: "2025-03-26T07:30:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      end: {
+        dateTime: "2025-03-26T08:15:00-05:00",
+        timeZone: "America/Chicago",
+      },
+      recurrence: ["RRULE:FREQ=DAILY"],
+      iCalUID: "68k0p6ackplecqs9fuvbs1fju0_R20250326T123000@google.com",
+      sequence: 0,
+      reminders: {
+        useDefault: true,
+      },
+      eventType: "default",
+    },
+  ],
+};
diff --git a/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/index.ts b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/index.ts
new file mode 100644
index 00000000..0e1f2e5f
--- /dev/null
+++ b/packages/backend/src/__tests__/mocks.gcal/mocks.gcal/fixtures/recurring/edit/this-and-following/index.ts
@@ -0,0 +1,7 @@
+import * as batch1 from "./batch1";
+import * as batch2 from "./batch2";
+
+export const thisAndFollowingPayloads = [
+  ...batch1.thisAndFollowingBatch1Payloads,
+  ...batch2.thisAndFollowingBatch2Payloads,
+];
diff --git a/packages/backend/src/sync/services/import/sync.import.test.ts b/packages/backend/src/sync/services/import/sync.import.test.ts
index 3d59516b..f929c72a 100644
--- a/packages/backend/src/sync/services/import/sync.import.test.ts
+++ b/packages/backend/src/sync/services/import/sync.import.test.ts
@@ -1,7 +1,7 @@
 import {
   cleanupTestMongo,
   clearCollections,
-  setupTestMongo,
+  setupTestDb,
 } from "@backend/__tests__/helpers/mock.db.setup";
 import { mockGcalEvents } from "@backend/__tests__/mocks.gcal/factories/gcal.event.factory";
 import { mockGcal } from "@backend/__tests__/mocks.gcal/factories/gcal.factory";
@@ -18,10 +18,10 @@ const totals = mockGcalEvents().totals;
 
 describe("SyncImport", () => {
   let syncImport: Awaited<ReturnType<typeof createSyncImport>>;
-  let setup: Awaited<ReturnType<typeof setupTestMongo>>;
+  let setup: Awaited<ReturnType<typeof setupTestDb>>;
 
   beforeAll(async () => {
-    setup = await setupTestMongo();
+    setup = await setupTestDb();
     syncImport = await createSyncImport(setup.userId);
   });
 
diff --git a/packages/backend/src/sync/services/notify/gcal.notification.handler.test.ts b/packages/backend/src/sync/services/notify/gcal.notification.handler.test.ts
new file mode 100644
index 00000000..46f5eba2
--- /dev/null
+++ b/packages/backend/src/sync/services/notify/gcal.notification.handler.test.ts
@@ -0,0 +1,144 @@
+import { gCalendar } from "@core/types/gcal";
+import {
+  cleanupTestMongo,
+  setupTestDb,
+} from "@backend/__tests__/helpers/mock.db.setup";
+import { SyncError } from "@backend/common/constants/error.constants";
+import { error } from "@backend/common/errors/handlers/error.handler";
+import gcalService from "@backend/common/services/gcal/gcal.service";
+import { getSync } from "@backend/sync/util/sync.queries";
+import { GCalNotificationHandler } from "./gcal.notification.handler";
+
+// Mock dependencies
+
+jest.mock("@backend/common/services/gcal/gcal.service", () => ({
+  __esModule: true,
+  default: {
+    getEvents: jest.fn(),
+  },
+}));
+
+jest.mock("@backend/sync/util/sync.queries", () => ({
+  getSync: jest.fn(),
+}));
+
+describe("GCalNotificationHandler", () => {
+  let handler: GCalNotificationHandler;
+  let mockGcal: gCalendar;
+  let mockUserId: string;
+  let setup: Awaited<ReturnType<typeof setupTestDb>>;
+  beforeAll(async () => {
+    setup = await setupTestDb();
+  });
+
+  beforeEach(() => {
+    mockUserId = "test-user-id";
+    mockGcal = {
+      events: {
+        list: jest.fn(),
+        get: jest.fn(),
+        insert: jest.fn(),
+        update: jest.fn(),
+        delete: jest.fn(),
+        instances: jest.fn(),
+      },
+    } as unknown as gCalendar;
+
+    handler = new GCalNotificationHandler(mockGcal, mockUserId);
+  });
+  afterAll(async () => {
+    await cleanupTestMongo(setup);
+  });
+
+  describe("handleNotification", () => {
+    const mockPayload = {
+      calendarId: "test-calendar-id",
+      resourceId: "test-channel-id",
+    };
+
+    const mockSync = {
+      user: mockUserId,
+      google: {
+        events: [
+          {
+            channelId: "test-channel-id",
+            gCalendarId: "test-calendar-id",
+            nextSyncToken: "test-sync-token",
+          },
+        ],
+      },
+    };
+
+    const mockEvents = [
+      {
+        id: "event-1",
+        summary: "Test Event",
+        start: { dateTime: "2024-03-20T10:00:00Z" },
+        end: { dateTime: "2024-03-20T11:00:00Z" },
+      },
+    ];
+
+    it("should process events successfully", async () => {
+      // Setup
+      (getSync as jest.Mock).mockResolvedValue(mockSync);
+      (gcalService.getEvents as jest.Mock).mockResolvedValue({
+        data: { items: mockEvents },
+      });
+
+      // Execute
+      const result = await handler.handleNotification(mockPayload);
+
+      // Verify
+      expect(getSync).toHaveBeenCalledWith({
+        resourceId: mockPayload.resourceId,
+      });
+      expect(gcalService.getEvents).toHaveBeenCalledWith(mockGcal, {
+        calendarId: mockPayload.calendarId,
+        syncToken: "test-sync-token",
+      });
+      expect(result).toEqual("CHANGES_PROCESSED");
+    });
+
+    it("should return NO_CHANGES when no changes found", async () => {
+      // Setup
+      (getSync as jest.Mock).mockResolvedValue(mockSync);
+      (gcalService.getEvents as jest.Mock).mockResolvedValue({
+        data: { items: [] },
+      });
+
+      // Execute and verify
+      const result = await handler.handleNotification(mockPayload);
+      expect(result).toEqual("NO_CHANGES");
+    });
+
+    it("should throw error when no sync record found", async () => {
+      // Setup
+      (getSync as jest.Mock).mockResolvedValue(null);
+
+      // Execute and verify
+      await expect(handler.handleNotification(mockPayload)).rejects.toEqual(
+        error(SyncError.NoSyncRecordForUser, expect.any(String)),
+      );
+    });
+
+    it("should throw error when no sync token found", async () => {
+      // Setup
+      (getSync as jest.Mock).mockResolvedValue({
+        ...mockSync,
+        google: {
+          events: [
+            {
+              channelId: "test-channel-id",
+              gCalendarId: "test-calendar-id",
+            },
+          ],
+        },
+      });
+
+      // Execute and verify
+      await expect(handler.handleNotification(mockPayload)).rejects.toEqual(
+        error(SyncError.NoSyncToken, expect.any(String)),
+      );
+    });
+  });
+});
diff --git a/packages/backend/src/sync/services/notify/sync.notify.recurring.test.ts b/packages/backend/src/sync/services/notify/sync.notify.recurring.test.ts
deleted file mode 100644
index 151cd27a..00000000
--- a/packages/backend/src/sync/services/notify/sync.notify.recurring.test.ts
+++ /dev/null
@@ -1,158 +0,0 @@
-// import { Origin } from "@core/constants/core.constants";
-// import { MapEvent } from "@core/mappers/map.event";
-// import { mockGcalEvent } from "@backend/__tests__/factories/gcal.event.factory";
-// import { mockGcal } from "@backend/__tests__/factories/gcal.factory";
-// import {
-//   cleanupTestMongo,
-//   clearCollections,
-//   setupTestMongo,
-// } from "@backend/__tests__/helpers/mock.db.setup";
-// import gcalService from "@backend/common/services/gcal/gcal.service";
-// import mongoService from "@backend/common/services/mongo.service";
-// import {
-//   ACTIONS_SYNC,
-//   SyncNotificationService,
-//   WS_RESULT,
-// } from "./sync.notify";
-
-// // Create a deterministic event factory for this specific test
-// const createRecurringEvent = () => {
-//   const TEST_EVENT_ID = "test-recurring-event";
-//   const TEST_INSTANCE_IDS = ["instance-1", "instance-2", "instance-3"];
-
-//   const baseDate = new Date("2024-03-20T10:00:00Z");
-//   const baseEvent = mockGcalEvent({
-//     id: TEST_EVENT_ID,
-//     summary: "Test Recurring Event",
-//     start: { dateTime: baseDate.toISOString() },
-//     end: { dateTime: new Date(baseDate.getTime() + 3600000).toISOString() },
-//     recurrence: ["RRULE:FREQ=WEEKLY"],
-//   });
-
-//   const instances = TEST_INSTANCE_IDS.map((id, index) => ({
-//     ...baseEvent,
-//     id,
-//     recurringEventId: TEST_EVENT_ID,
-//     start: {
-//       dateTime: new Date(
-//         baseDate.getTime() + index * 7 * 24 * 60 * 60 * 1000,
-//       ).toISOString(),
-//       timeZone: "UTC",
-//     },
-//     end: {
-//       dateTime: new Date(
-//         baseDate.getTime() + index * 7 * 24 * 60 * 60 * 1000 + 3600000,
-//       ).toISOString(),
-//       timeZone: "UTC",
-//     },
-//   }));
-
-//   return { baseEvent, instances, TEST_EVENT_ID, TEST_INSTANCE_IDS };
-// };
-
-// // Mock Google Calendar API responses
-// jest.mock("googleapis", () => {
-//   const { baseEvent, instances } = createRecurringEvent();
-//   return mockGcal({
-//     events: [baseEvent, ...instances],
-//     nextSyncToken: "test-sync-token",
-//   });
-// });
-
-// const setupTestDatabase = async (userId: string) => {
-//   const { baseEvent, instances } = createRecurringEvent();
-
-//   // Map to Compass events
-//   const compassEvents = [
-//     ...MapEvent.toCompass(userId, [baseEvent], Origin.GOOGLE_IMPORT),
-//     ...MapEvent.toCompass(userId, instances, Origin.GOOGLE_IMPORT),
-//   ].filter((event): event is NonNullable<typeof event> => event !== undefined);
-
-//   // Insert into database
-//   await mongoService.event.insertMany(compassEvents);
-
-//   return {
-//     baseEvent,
-//     instances,
-//     compassEvents,
-//   };
-// };
-
-// describe("Recurring Event Updates", () => {
-//   let syncNotifyService: SyncNotificationService;
-//   let setup: Awaited<ReturnType<typeof setupTestMongo>>;
-//   let testData: Awaited<ReturnType<typeof setupTestDatabase>>;
-
-//   beforeAll(async () => {
-//     setup = await setupTestMongo();
-//     syncNotifyService = new SyncNotificationService();
-//     testData = await setupTestDatabase(setup.userId);
-//   });
-
-//   beforeEach(async () => {
-//     await clearCollections(setup.db);
-//     // Re-setup the database for each test
-//     testData = await setupTestDatabase(setup.userId);
-//   });
-
-//   afterAll(async () => {
-//     await cleanupTestMongo(setup);
-//   });
-
-it.todo("should update only the modified instance");
-//   it("should update only the modified instance", async () => {
-//     // Create a modified version of one instance
-//     const modifiedInstance = {
-//       ...testData.instances[1],
-//       summary: "Modified Instance",
-//     };
-
-//     // Mock the Google Calendar API to return the modified instance
-//     jest.spyOn(gcalService, "getEvents").mockResolvedValueOnce({
-//       config: {},
-//       data: {
-//         items: [modifiedInstance],
-//         nextSyncToken: "new-sync-token",
-//       },
-//       status: 200,
-//       statusText: "OK",
-//       headers: {},
-//       request: {
-//         responseURL:
-//           "https://www.googleapis.com/calendar/v3/calendars/primary/events",
-//       },
-//     });
-
-//     // Trigger notification
-//     const result = await syncNotifyService.handleGcalNotification({
-//       channelId: "test-channel-id",
-//       resourceId: "test-resource-id",
-//       resourceState: "exists",
-//       expiration: new Date(Date.now() + 3600000).toISOString(),
-//     });
-
-//     // Verify notification processing
-//     expect(result.action).toBe(ACTIONS_SYNC.PROCESSED);
-//     expect(result.updated).toBe(1);
-//     expect(result.created).toBe(0);
-//     expect(result.wsResult).not.toBe(WS_RESULT.UNPROCESSED);
-
-//     // Assertions
-//     const updatedEvents = await mongoService.event.find().toArray();
-
-//     // Check that only the modified instance was updated
-//     const modifiedEvent = updatedEvents.find(
-//       (e) => e.gEventId === modifiedInstance.id,
-//     );
-//     expect(modifiedEvent?.title).toBe("Modified Instance");
-
-//     // Check that other instances remain unchanged
-//     const unchangedEvents = updatedEvents.filter(
-//       (e) => e.gEventId !== modifiedInstance.id,
-//     );
-//     expect(unchangedEvents).toHaveLength(testData.instances.length - 1);
-//     unchangedEvents.forEach((event) => {
-//       expect(event.title).toBe(testData.baseEvent.summary);
-//     });
-//   });
-// });
diff --git a/packages/backend/src/sync/services/notify/sync.notify.test.setup.ts b/packages/backend/src/sync/services/notify/sync.notify.test.setup.ts
deleted file mode 100644
index 595bd6ce..00000000
--- a/packages/backend/src/sync/services/notify/sync.notify.test.setup.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import {
-  Origin as MockOrigin,
-  Priorities as MockPriorities,
-} from "@core/constants/core.constants";
-import { gSchema$Event } from "@core/types/gcal";
-import { mockGcal } from "@backend/__tests__/mocks.gcal/factories/gcal.factory";
-
-const TEST_EVENT_ID = "test-event-id";
-
-const mockRegularEvent: gSchema$Event = {
-  id: TEST_EVENT_ID,
-  summary: "Updated Event",
-  status: "confirmed",
-  htmlLink: "https://www.google.com/calendar/event?eid=test-event-id",
-  created: "2025-03-19T10:32:57.036Z",
-  updated: "2025-03-19T10:32:57.036Z",
-  start: {
-    dateTime: "2025-03-19T14:45:00-05:00",
-    timeZone: "America/Chicago",
-  },
-  end: {
-    dateTime: "2025-03-19T16:00:00-05:00",
-    timeZone: "America/Chicago",
-  },
-  iCalUID: "test-event-id@google.com",
-  sequence: 0,
-  extendedProperties: {
-    private: {
-      origin: MockOrigin.GOOGLE_IMPORT,
-      priority: MockPriorities.UNASSIGNED,
-    },
-  },
-  reminders: {
-    useDefault: true,
-  },
-  eventType: "default",
-};
-
-export const mockGcalEvents = () => {
-  jest.mock("googleapis", () => {
-    return mockGcal({
-      events: [mockRegularEvent],
-      nextSyncToken: "new-sync-token",
-    });
-  });
-};
diff --git a/packages/backend/src/sync/services/notify/sync.notify.test.ts b/packages/backend/src/sync/services/notify/sync.notify.test.ts
deleted file mode 100644
index 059e57bb..00000000
--- a/packages/backend/src/sync/services/notify/sync.notify.test.ts
+++ /dev/null
@@ -1,101 +0,0 @@
-import { ObjectId } from "mongodb";
-import {
-  Origin as MockOrigin,
-  Priorities as MockPriorities,
-} from "@core/constants/core.constants";
-import { Schema_Event } from "@core/types/event.types";
-import {
-  cleanupTestMongo,
-  clearCollections,
-  setupTestMongo,
-} from "@backend/__tests__/helpers/mock.db.setup";
-import { mockGcalEvent } from "@backend/__tests__/mocks.gcal/factories/gcal.event.factory";
-import { mockGcal } from "@backend/__tests__/mocks.gcal/factories/gcal.factory";
-import mongoService from "@backend/common/services/mongo.service";
-import {
-  ACTIONS_SYNC,
-  SyncNotificationService,
-  WS_RESULT,
-} from "./sync.notify";
-
-const TEST_EVENT_ID = "test-event-id";
-
-// Mock Google Calendar API responses
-jest.mock("googleapis", () => {
-  const mockEvent = mockGcalEvent({
-    id: TEST_EVENT_ID,
-    summary: "Updated Event",
-    extendedProperties: {
-      private: {
-        origin: MockOrigin.GOOGLE_IMPORT,
-        priority: MockPriorities.UNASSIGNED,
-      },
-    },
-  });
-
-  return mockGcal({
-    events: [mockEvent],
-    nextSyncToken: "new-sync-token",
-  });
-});
-
-describe("SyncNotificationService", () => {
-  let syncNotifyService: SyncNotificationService;
-  let setup: Awaited<ReturnType<typeof setupTestMongo>>;
-
-  beforeAll(async () => {
-    setup = await setupTestMongo();
-    syncNotifyService = new SyncNotificationService();
-  });
-
-  beforeEach(async () => {
-    await clearCollections(setup.db);
-  });
-
-  afterAll(async () => {
-    await cleanupTestMongo(setup);
-  });
-
-  describe("Regular Events", () => {
-    it("should update existing event instead of creating duplicate when receiving notification", async () => {
-      // Create initial event in Compass
-      const initialEvent: Schema_Event = {
-        _id: new ObjectId(),
-        user: setup.userId,
-        gEventId: TEST_EVENT_ID,
-        title: "Initial Event",
-        origin: MockOrigin.COMPASS,
-        startDate: new Date("2025-03-19T14:45:00-05:00").toISOString(),
-        endDate: new Date("2025-03-19T16:00:00-05:00").toISOString(),
-        isAllDay: false,
-        isSomeday: false,
-        priority: MockPriorities.UNASSIGNED,
-        updatedAt: new Date(),
-      };
-      await mongoService.event.insertOne(initialEvent);
-
-      // Process notification
-      const result = await syncNotifyService.handleGcalNotification({
-        channelId: "test-channel-id",
-        resourceId: "test-resource-id",
-        resourceState: "exists",
-        expiration: new Date(Date.now() + 3600000).toISOString(),
-      });
-
-      // Verify business logic
-      expect(result.action).toBe(ACTIONS_SYNC.PROCESSED);
-      expect(result.updated).toBe(1);
-      expect(result.created).toBe(0);
-      expect(result.wsResult).not.toBe(WS_RESULT.UNPROCESSED);
-
-      // Verify DB state
-      const events = await mongoService.event
-        .find({ gEventId: TEST_EVENT_ID })
-        .toArray();
-      expect(events).toHaveLength(1);
-      expect(events[0]).toBeDefined();
-      expect(events[0]?.["title"]).toBe("Updated Event");
-      expect(events[0]?.["origin"]).toBe(MockOrigin.GOOGLE_IMPORT);
-    });
-  });
-});
diff --git a/packages/core/src/__tests__/mappers/map.event.test.ts b/packages/core/src/__tests__/mappers/map.event.test.ts
index 64d228dc..cb465069 100644
--- a/packages/core/src/__tests__/mappers/map.event.test.ts
+++ b/packages/core/src/__tests__/mappers/map.event.test.ts
@@ -112,31 +112,55 @@ describe("toCompass", () => {
 
   const allEvents = [...eventsFromCompass, ...eventsFromGcalImport];
 
-  it("sets priority to unassigned by default", () => {
-    const gEvent = (gcalEvents.items as gSchema$Event[]).find(
-      (ge) => ge.summary === "No extendedProperties",
-    );
-    if (!gEvent) {
-      throw new Error("Test event not found");
-    }
+  describe("cancelled", () => {
+    it("skips cancelled events", () => {
+      const events = MapEvent.toCompass(
+        "someId",
+        gcalEvents.items as gSchema$Event[],
+        Origin.GOOGLE,
+      );
 
-    const cEvent = MapEvent.toCompass("user1", [gEvent], Origin.COMPASS)[0];
-    if (!cEvent) {
-      throw new Error("Failed to map event");
-    }
+      const hasCancelledEvent = events.some((e: Schema_Event) => {
+        return (e as any).status === "cancelled";
+      });
 
-    expect(cEvent.priority).toBe(Priorities.UNASSIGNED);
+      expect(hasCancelledEvent).toBe(false);
+    });
   });
 
-  it("infers isAllDay when date is in YYYY-MM-DD format", () => {
-    allEvents.forEach((e) => {
-      if (e.startDate.length === "YYYY-MM-DD".length) {
-        expect(e.isAllDay).toBe(true);
-      }
+  describe("origin", () => {
+    it("uses an expected origin", () => {
+      allEvents.forEach((ce) => {
+        expect(Object.values(Origin).includes(ce.origin)).toBe(true);
+      });
+    });
+  });
+  describe("isAllDay", () => {
+    it("infers isAllDay when date is in YYYY-MM-DD format", () => {
+      allEvents.forEach((e) => {
+        if (e.startDate.length === "YYYY-MM-DD".length) {
+          expect(e.isAllDay).toBe(true);
+        }
+      });
     });
   });
+  describe("priority", () => {
+    it("sets priority to unassigned by default", () => {
+      const gEvent = (gcalEvents.items as gSchema$Event[]).find(
+        (ge) => ge.summary === "No extendedProperties",
+      );
+      if (!gEvent) {
+        throw new Error("Test event not found");
+      }
+
+      const cEvent = MapEvent.toCompass("user1", [gEvent], Origin.COMPASS)[0];
+      if (!cEvent) {
+        throw new Error("Failed to map event");
+      }
+
+      expect(cEvent.priority).toBe(Priorities.UNASSIGNED);
+    });
 
-  describe("from Gcal", () => {
     it("gets priority from private extended properties", () => {
       const regularGcalEvent = (gcalEvents.items as gSchema$Event[]).find(
         (ge) => ge.summary === "Meeting with Stan",
@@ -156,29 +180,55 @@ describe("toCompass", () => {
 
       expect(cEvent.priority).toBe("work");
     });
+  });
 
-    it("skips cancelled events", () => {
-      const events = MapEvent.toCompass(
-        "someId",
-        gcalEvents.items as gSchema$Event[],
-        Origin.GOOGLE,
-      );
-
-      const hasCancelledEvent = events.some((e: Schema_Event) => {
-        return (e as any).status === "cancelled";
-      });
+  describe("recurrence", () => {
+    it("does not include both recurrence rule and id simultaneously", () => {
+      const gEvent = {
+        kind: "calendar#event",
+        etag: '"3487376669522302"',
+        id: "7q78dn5t1eu6ikjq5mj4q7s93d_R20250403T120000",
+        status: "confirmed",
+        htmlLink:
+          "https://www.google.com/calendar/event?eid=N3E3OGRuNXQxZXU2aWtqcTVtajRxN3M5M2RfMjAyNTA0MDNUMTIwMDAwWiBsYW5jZS5lc3NlcnRAbQ",
+        created: "2025-04-03T13:49:29.000Z",
+        updated: "2025-04-03T13:52:14.761Z",
+        summary: "r1-i",
+        creator: {
+          email: "test.user@gmail.com",
+          self: true,
+        },
+        organizer: {
+          email: "test.user@gmail.com",
+          self: true,
+        },
+        start: {
+          dateTime: "2025-04-03T07:00:00-05:00",
+          timeZone: "America/Chicago",
+        },
+        end: {
+          dateTime: "2025-04-03T08:00:00-05:00",
+          timeZone: "America/Chicago",
+        },
+        recurrence: ["RRULE:FREQ=DAILY"],
+        iCalUID: "7q78dn5t1eu6ikjq5mj4q7s93d_R20250403T120000@google.com",
+        sequence: 0,
+        reminders: {
+          useDefault: true,
+        },
+        eventType: "default",
+      };
 
-      expect(hasCancelledEvent).toBe(false);
-    });
+      const cEvent = MapEvent.toCompass("user1", [gEvent], Origin.COMPASS)[0];
+      if (!cEvent) {
+        throw new Error("Failed to map event");
+      }
 
-    it("uses an expected origin", () => {
-      allEvents.forEach((ce) => {
-        expect(Object.values(Origin).includes(ce.origin)).toBe(true);
-      });
+      expect(cEvent.recurrence).toBeDefined();
+      expect(cEvent.recurrence?.eventId).toBeUndefined();
     });
-
-    it("includes recurrence when rule is present", () => {
-      const gEvent = recurring[0]?.items[0] as gSchema$Event | undefined;
+    it("includes recurrence for instances of recurring events", () => {
+      const gEvent = recurring[0]?.items[1] as gSchema$Event | undefined;
       if (!gEvent) {
         throw new Error("Test event not found in mock data");
       }
@@ -194,12 +244,11 @@ describe("toCompass", () => {
       }
 
       expect(cEvent.recurrence).toBeDefined();
-      expect(cEvent.recurrence?.rule).toEqual(["RRULE:FREQ=DAILY"]);
-      expect(cEvent.recurrence?.eventId).toBe(gEvent.id);
+      expect(cEvent.recurrence?.rule).toBeUndefined();
+      expect(cEvent.recurrence?.eventId).toBe(gEvent.recurringEventId);
     });
-
-    it("includes recurrence for instances of recurring events", () => {
-      const gEvent = recurring[0]?.items[1] as gSchema$Event | undefined;
+    it("includes recurrence when rule is present", () => {
+      const gEvent = recurring[0]?.items[0] as gSchema$Event | undefined;
       if (!gEvent) {
         throw new Error("Test event not found in mock data");
       }
@@ -215,8 +264,8 @@ describe("toCompass", () => {
       }
 
       expect(cEvent.recurrence).toBeDefined();
-      expect(cEvent.recurrence?.rule).toEqual([]);
-      expect(cEvent.recurrence?.eventId).toBe(gEvent.recurringEventId);
+      expect(cEvent.recurrence?.rule).toEqual(["RRULE:FREQ=DAILY"]);
+      expect(cEvent.recurrence?.eventId).toBeUndefined();
     });
   });
 });