From 0e4fb124dd50a33e6e9249c01779ea121dea2800 Mon Sep 17 00:00:00 2001 From: meetul Date: Wed, 14 Feb 2024 14:06:29 +0530 Subject: [PATCH] add tests --- ...reateRecurringEventInstancesDuringQuery.ts | 52 +-- .../generateRecurringEventInstances.ts | 24 +- .../eventsByOrganizationConnection.spec.ts | 314 ++++++++++++++++-- 3 files changed, 327 insertions(+), 63 deletions(-) diff --git a/src/helpers/event/createEventHelpers/createRecurringEventInstancesDuringQuery.ts b/src/helpers/event/createEventHelpers/createRecurringEventInstancesDuringQuery.ts index 6ebd1e9934..3599e1e6f6 100644 --- a/src/helpers/event/createEventHelpers/createRecurringEventInstancesDuringQuery.ts +++ b/src/helpers/event/createEventHelpers/createRecurringEventInstancesDuringQuery.ts @@ -8,7 +8,7 @@ import { } from "../recurringEventHelpers"; import { session } from "../../../db"; import type { Recurrance } from "../../../types/generatedGraphQLTypes"; -import type { InterfaceGenerateRecurringInstancesData } from "../recurringEventHelpers/generateRecurringEventInstances"; +import type { InterfaceRecurringEvent } from "../recurringEventHelpers/generateRecurringEventInstances"; import { RECURRING_EVENT_INSTANCES_QUERY_LIMIT } from "../../../constants"; /** @@ -18,6 +18,7 @@ import { RECURRING_EVENT_INSTANCES_QUERY_LIMIT } from "../../../constants"; * 1. Get the limit date upto which we would want to query the recurrenceRules and generate new instances. * 2. Get the recurrence rules to be used for instance generation during this query. * 3. For every recurrence rule found: + * - find the base recurring event to get the data to be used for new instance generation. * - get the number of existing instances and how many more to generate based on the recurrenceRule's count (if specified). * - generate new instances after their latestInstanceDates. * - update the latestInstanceDate. @@ -46,43 +47,48 @@ export const createRecurringEventInstancesDuringQuery = async ( await Promise.all( recurrenceRules.map(async (recurrenceRule) => { // find the baseRecurringEvent for the recurrenceRule - const baseRecurringEvent = await Event.findOne({ + const baseRecurringEvent = await Event.find({ _id: recurrenceRule.baseRecurringEventId, }).lean(); - if (!baseRecurringEvent) { - throw new Error(); - } - // get the data from the baseRecurringEvent - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { _id, recurrance, ...data } = baseRecurringEvent; + const { + _id: baseRecurringEventId, + recurrance, + ...data + } = baseRecurringEvent[0]; // get the input data for the generateRecurringEventInstances function - const currentInputData: InterfaceGenerateRecurringInstancesData = { + const currentInputData: InterfaceRecurringEvent = { ...data, organizationId: recurrenceRule.organizationId.toString(), recurrance: recurrance as Recurrance, }; - // get the latestInstanceDate of the current recurrenceRule - const latestInstanceDate = recurrenceRule.latestInstanceDate; + // get the properties from recurrenceRule + const { + _id: recurrenceRuleId, + latestInstanceDate, + recurrenceRuleString, + endDate: recurrenceEndDate, + count: totalInstancesCount, + } = recurrenceRule; + // get the date from which new instances would be generated - const recurrenceStartDate = addDays(latestInstanceDate, 1); + const currentRecurrenceStartDate = addDays(latestInstanceDate, 1); // get the dates for recurrence let recurringInstanceDates = getRecurringInstanceDates( - recurrenceRule.recurrenceRuleString, - recurrenceStartDate, - recurrenceRule.endDate, + recurrenceRuleString, + currentRecurrenceStartDate, + recurrenceEndDate, queryUptoDate ); // find out how many instances following the recurrence rule already exist and how many more to generate - const { count: totalInstancesCount } = recurrenceRule; if (totalInstancesCount) { const totalExistingInstances = await Event.countDocuments({ - recurrenceRuleId: recurrenceRule._id, + recurrenceRuleId, }); const remainingInstances = totalInstancesCount - totalExistingInstances; @@ -100,7 +106,6 @@ export const createRecurringEventInstancesDuringQuery = async ( } /* c8 ignore stop */ - try { if (recurringInstanceDates && recurringInstanceDates.length) { const updatedLatestRecurringInstanceDate = @@ -109,7 +114,7 @@ export const createRecurringEventInstancesDuringQuery = async ( // update the latestInstanceDate of the recurrenceRule await RecurrenceRule.updateOne( { - _id: recurrenceRule._id, + _id: recurrenceRuleId, }, { latestInstanceDate: updatedLatestRecurringInstanceDate, @@ -120,10 +125,10 @@ export const createRecurringEventInstancesDuringQuery = async ( // generate recurring event instances await generateRecurringEventInstances({ data: currentInputData, - baseRecurringEventId: baseRecurringEvent._id.toString(), - recurrenceRuleId: recurrenceRule._id.toString(), + baseRecurringEventId: baseRecurringEventId.toString(), + recurrenceRuleId: recurrenceRuleId.toString(), recurringInstanceDates, - creatorId: baseRecurringEvent.creatorId, + creatorId: baseRecurringEvent[0].creatorId.toString(), organizationId, session, }); @@ -134,10 +139,7 @@ export const createRecurringEventInstancesDuringQuery = async ( // commit transaction if everything's successful await session.commitTransaction(); } - - /* c8 ignore stop */ } catch (error) { - /* c8 ignore start */ if (session) { // abort transaction if something fails await session.abortTransaction(); diff --git a/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts b/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts index 03db0267a2..e76992c152 100644 --- a/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts +++ b/src/helpers/event/recurringEventHelpers/generateRecurringEventInstances.ts @@ -20,14 +20,8 @@ import { cacheEvents } from "../../../services/EventCache/cacheEvents"; * @returns A recurring instance generated during this operation. */ -export interface InterfaceGenerateRecurringInstancesData extends EventInput { - attendees?: string; - admins?: string[]; - status?: string; -} - interface InterfaceGenerateRecurringInstances { - data: InterfaceGenerateRecurringInstancesData; + data: InterfaceRecurringEvent; baseRecurringEventId: string; recurrenceRuleId: string; recurringInstanceDates: Date[]; @@ -37,14 +31,13 @@ interface InterfaceGenerateRecurringInstances { session: mongoose.ClientSession; } -interface InterfaceRecurringEvent extends EventInput { - attendees?: string; - isBaseRecurringEvent: boolean; - recurrenceRuleId: string; - baseRecurringEventId: string; - creatorId: string; - admins: string[]; - organization: string; +export interface InterfaceRecurringEvent extends EventInput { + isBaseRecurringEvent?: boolean; + recurrenceRuleId?: string; + baseRecurringEventId?: string; + creatorId?: string; + admins?: string[]; + organization?: string; status?: string; } @@ -61,7 +54,6 @@ export const generateRecurringEventInstances = async ({ recurringInstanceDates.map((date): void => { const createdEventInstance = { ...data, - attendees: data.attendees, startDate: date, endDate: date, recurring: true, diff --git a/tests/resolvers/Query/eventsByOrganizationConnection.spec.ts b/tests/resolvers/Query/eventsByOrganizationConnection.spec.ts index 5e450709bc..bffada8d07 100644 --- a/tests/resolvers/Query/eventsByOrganizationConnection.spec.ts +++ b/tests/resolvers/Query/eventsByOrganizationConnection.spec.ts @@ -1,21 +1,50 @@ -// @ts-nocheck import "dotenv/config"; import { eventsByOrganizationConnection as eventsByOrganizationConnectionResolver } from "../../../src/resolvers/Query/eventsByOrganizationConnection"; -import { connect, disconnect } from "../../helpers/db"; +import { + connect, + disconnect, + dropAllCollectionsFromDatabase, +} from "../../helpers/db"; import type mongoose from "mongoose"; -import type { QueryEventsByOrganizationConnectionArgs } from "../../../src/types/generatedGraphQLTypes"; +import type { + MutationCreateEventArgs, + QueryEventsByOrganizationConnectionArgs, +} from "../../../src/types/generatedGraphQLTypes"; +import type { InterfaceEvent } from "../../../src/models"; import { Event } from "../../../src/models"; -import { createTestUserAndOrganization } from "../../helpers/userAndOrg"; +import type { + TestOrganizationType} from "../../helpers/userAndOrg"; +import { + createTestUserAndOrganization, +} from "../../helpers/userAndOrg"; import type { TestEventType } from "../../helpers/events"; import { createEventWithRegistrant } from "../../helpers/events"; -import { beforeAll, afterAll, describe, it, expect } from "vitest"; +import { beforeAll, afterAll, describe, it, expect, vi } from "vitest"; +import { addDays, addYears } from "date-fns"; +import { convertToUTCDate } from "../../../src/utilities/recurrenceDatesUtil"; +import type { TestUserType } from "../../helpers/user"; +import type { + InterfaceRecurrenceRule} from "../../../src/models/RecurrenceRule"; +import { + Frequency, + RecurrenceRule, +} from "../../../src/models/RecurrenceRule"; +import { + RECURRING_EVENT_INSTANCES_DAILY_LIMIT, + RECURRING_EVENT_INSTANCES_WEEKLY_LIMIT, +} from "../../../src/constants"; +import { rrulestr } from "rrule"; +import type { RRule } from "rrule"; let MONGOOSE_INSTANCE: typeof mongoose; let testEvents: TestEventType[]; +let testUser: TestUserType; +let testOrganization: TestOrganizationType; beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); - const [testUser, testOrganization] = await createTestUserAndOrganization(); + await dropAllCollectionsFromDatabase(MONGOOSE_INSTANCE); + [testUser, testOrganization] = await createTestUserAndOrganization(); const testEvent1 = await createEventWithRegistrant( testUser?._id, testOrganization?._id, @@ -38,6 +67,7 @@ beforeAll(async () => { }); afterAll(async () => { + await dropAllCollectionsFromDatabase(MONGOOSE_INSTANCE); await disconnect(MONGOOSE_INSTANCE); }); @@ -92,7 +122,11 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { const events = await Event.find(where).sort(sort).lean(); let eventsByOrganizationConnectionPayload = - await eventsByOrganizationConnectionResolver?.({}, args, {}); + (await eventsByOrganizationConnectionResolver?.( + {}, + args, + {} + )) as InterfaceEvent[]; eventsByOrganizationConnectionPayload = eventsByOrganizationConnectionPayload?.map((event) => { @@ -145,7 +179,11 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { const events = await Event.find(where).limit(2).skip(1).sort(sort).lean(); let eventsByOrganizationConnectionPayload = - await eventsByOrganizationConnectionResolver?.({}, args, {}); + (await eventsByOrganizationConnectionResolver?.( + {}, + args, + {} + )) as InterfaceEvent[]; eventsByOrganizationConnectionPayload = eventsByOrganizationConnectionPayload?.map((event) => { @@ -172,10 +210,10 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { $nin: [testEvents[0]?._id], }, title: { - $nin: [testEvents[0]?.title], + $nin: [testEvents[0]?.title ?? ""], }, description: { - $nin: [testEvents[0]?.description], + $nin: [testEvents[0]?.description ?? ""], }, location: { $nin: [testEvents[0]?.location], @@ -185,9 +223,9 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { const args: QueryEventsByOrganizationConnectionArgs = { where: { id_not_in: [testEvents[0]?._id], - title_not_in: [testEvents[0]?.title], - description_not_in: [testEvents[0]?.description], - location_not_in: [testEvents[0]?.location], + title_not_in: [testEvents[0]?.title ?? ""], + description_not_in: [testEvents[0]?.description ?? ""], + location_not_in: [testEvents[0]?.location ?? ""], }, orderBy: "title_DESC", }; @@ -195,7 +233,11 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { const events = await Event.find(where).sort(sort).lean(); let eventsByOrganizationConnectionPayload = - await eventsByOrganizationConnectionResolver?.({}, args, {}); + (await eventsByOrganizationConnectionResolver?.( + {}, + args, + {} + )) as InterfaceEvent[]; eventsByOrganizationConnectionPayload = eventsByOrganizationConnectionPayload?.map((event) => { @@ -224,10 +266,10 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { $in: [testEvents[0]?._id], }, title: { - $in: [testEvents[0]?.title], + $in: [testEvents[0]?.title ?? ""], }, description: { - $in: [testEvents[0]?.description], + $in: [testEvents[0]?.description ?? ""], }, location: { $in: [testEvents[0]?.location], @@ -239,9 +281,9 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { skip: 1, where: { id_in: [testEvents[0]?._id], - title_in: [testEvents[0]?.title], - description_in: [testEvents[0]?.description], - location_in: [testEvents[0]?.location], + title_in: [testEvents[0]?.title ?? ""], + description_in: [testEvents[0]?.description ?? ""], + location_in: [testEvents[0]?.location ?? ""], }, orderBy: "title_ASC", }; @@ -249,7 +291,11 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { const events = await Event.find(where).limit(2).skip(1).sort(sort).lean(); let eventsByOrganizationConnectionPayload = - await eventsByOrganizationConnectionResolver?.({}, args, {}); + (await eventsByOrganizationConnectionResolver?.( + {}, + args, + {} + )) as InterfaceEvent[]; eventsByOrganizationConnectionPayload = eventsByOrganizationConnectionPayload?.map((event) => { @@ -300,7 +346,11 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { const events = await Event.find(where).limit(2).skip(1).sort(sort).lean(); let eventsByOrganizationConnectionPayload = - await eventsByOrganizationConnectionResolver?.({}, args, {}); + (await eventsByOrganizationConnectionResolver?.( + {}, + args, + {} + )) as InterfaceEvent[]; eventsByOrganizationConnectionPayload = eventsByOrganizationConnectionPayload?.map((event) => { @@ -340,7 +390,11 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { const events = await Event.find(where).limit(2).skip(1).sort(sort).lean(); let eventsByOrganizationConnectionPayload = - await eventsByOrganizationConnectionResolver?.({}, args, {}); + (await eventsByOrganizationConnectionResolver?.( + {}, + args, + {} + )) as InterfaceEvent[]; eventsByOrganizationConnectionPayload = eventsByOrganizationConnectionPayload?.map((event) => { @@ -356,4 +410,220 @@ describe("resolvers -> Query -> organizationsMemberConnection", () => { }); expect(eventsByOrganizationConnectionPayload).toEqual(events); }); + + it("dynamic recurring event instances generation during query for events with no end dates", async () => { + vi.useFakeTimers(); + + const startDate = convertToUTCDate(new Date()); + + const eventArgs: MutationCreateEventArgs = { + data: { + organizationId: testOrganization?.id, + allDay: true, + description: "newDescription", + isPublic: false, + isRegisterable: false, + latitude: 1, + longitude: 1, + location: "newLocation", + recurring: true, + startDate, + startTime: startDate.toUTCString(), + title: "newTitle", + recurrance: "ONCE", + }, + recurrenceRuleData: { + frequency: "DAILY", + }, + }; + + const context = { + userId: testUser?.id, + }; + const { createEvent: createEventResolver } = await import( + "../../../src/resolvers/Mutation/createEvent" + ); + + const createEventPayload = await createEventResolver?.( + {}, + eventArgs, + context + ); + + expect(createEventPayload).toEqual( + expect.objectContaining({ + allDay: true, + description: "newDescription", + isPublic: false, + isRegisterable: false, + latitude: 1, + longitude: 1, + location: "newLocation", + recurring: true, + title: "newTitle", + creatorId: testUser?._id, + admins: expect.arrayContaining([testUser?._id]), + organization: testOrganization?._id, + }) + ); + + let recurrenceRule = await RecurrenceRule.findOne({ + frequency: Frequency.DAILY, + startDate, + }); + + const currentlatestInstanceDate = addYears( + startDate, + RECURRING_EVENT_INSTANCES_DAILY_LIMIT + ); + expect(recurrenceRule?.latestInstanceDate).toEqual( + currentlatestInstanceDate + ); + + const newMockDate = addDays(startDate, 1); + vi.setSystemTime(newMockDate); + + const args: QueryEventsByOrganizationConnectionArgs = { + where: { + organization_id: testOrganization?._id, + }, + }; + + await eventsByOrganizationConnectionResolver?.({}, args, {}); + + recurrenceRule = await RecurrenceRule.findOne({ + frequency: Frequency.DAILY, + startDate, + }); + + const newLatestInstanceDate = addYears( + addYears(newMockDate, 1), + RECURRING_EVENT_INSTANCES_DAILY_LIMIT + ); + + expect(recurrenceRule?.latestInstanceDate).toEqual(newLatestInstanceDate); + + vi.useRealTimers(); + }); + + it("dynamic recurring event instances generation during query for specified number of instances", async () => { + vi.useFakeTimers(); + + const startDate = convertToUTCDate(new Date()); + + const eventArgs: MutationCreateEventArgs = { + data: { + organizationId: testOrganization?.id, + allDay: true, + description: "newDescription", + isPublic: false, + isRegisterable: false, + latitude: 1, + longitude: 1, + location: "newLocation", + recurring: true, + startDate, + startTime: startDate.toUTCString(), + title: "newTitle", + recurrance: "ONCE", + }, + recurrenceRuleData: { + frequency: "WEEKLY", + count: 150, + }, + }; + + const context = { + userId: testUser?.id, + }; + const { createEvent: createEventResolver } = await import( + "../../../src/resolvers/Mutation/createEvent" + ); + + const createEventPayload = await createEventResolver?.( + {}, + eventArgs, + context + ); + + expect(createEventPayload).toEqual( + expect.objectContaining({ + allDay: true, + description: "newDescription", + isPublic: false, + isRegisterable: false, + latitude: 1, + longitude: 1, + location: "newLocation", + recurring: true, + title: "newTitle", + creatorId: testUser?._id, + admins: expect.arrayContaining([testUser?._id]), + organization: testOrganization?._id, + }) + ); + + let recurrenceRule = await RecurrenceRule.findOne({ + frequency: Frequency.WEEKLY, + startDate, + }); + + const { recurrenceRuleString } = recurrenceRule as InterfaceRecurrenceRule; + const recurrenceRuleObject: RRule = rrulestr(recurrenceRuleString); + + const generateUptoDate = addYears( + startDate, + RECURRING_EVENT_INSTANCES_WEEKLY_LIMIT + ); + const currentLatestInstanceDate = recurrenceRuleObject.before( + generateUptoDate, + true + ); + + expect(recurrenceRule?.latestInstanceDate).toEqual( + currentLatestInstanceDate + ); + + const generatedWeeklyRecurringInstances = await Event.find({ + recurrenceRuleId: recurrenceRule?._id, + }); + + expect(generatedWeeklyRecurringInstances.length).toBeLessThan(150); + + const newMockDate = addDays(currentLatestInstanceDate as Date, 1); + vi.setSystemTime(newMockDate); + + const args: QueryEventsByOrganizationConnectionArgs = { + where: { + organization_id: testOrganization?._id, + }, + }; + + await eventsByOrganizationConnectionResolver?.({}, args, {}); + + recurrenceRule = await RecurrenceRule.findOne({ + frequency: Frequency.WEEKLY, + startDate, + }); + + const newGenerateUptoDate = addYears( + newMockDate, + RECURRING_EVENT_INSTANCES_WEEKLY_LIMIT + ); + + const newLatestInstanceDate = recurrenceRuleObject.before( + newGenerateUptoDate, + true + ); + + expect(recurrenceRule?.latestInstanceDate).toEqual(newLatestInstanceDate); + + const allWeeklyRecurringEventInstances = await Event.find({ + recurrenceRuleId: recurrenceRule?._id, + }); + + expect(allWeeklyRecurringEventInstances.length).toEqual(150); + + vi.useRealTimers(); + }); });