Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions apps/web/test/utils/bookingScenario/bookingScenario.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2132,6 +2132,20 @@ export function mockVideoAppToCrashOnCreateMeeting({
});
}

export function mockVideoAppToCrashOnUpdateMeeting({
metadataLookupKey,
appStoreLookupKey,
}: {
metadataLookupKey: string;
appStoreLookupKey?: string;
}) {
return mockVideoApp({
metadataLookupKey,
appStoreLookupKey,
updationCrash: true,
});
}

export function mockPaymentApp({
metadataLookupKey,
appStoreLookupKey,
Expand Down
14 changes: 9 additions & 5 deletions packages/app-store-cli/src/build.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import chokidar from "chokidar";
import fs from "fs";
// eslint-disable-next-line no-restricted-imports
import { debounce } from "lodash";
import path from "path";
import prettier from "prettier";
Expand All @@ -22,7 +21,7 @@ const formatOutput = (source: string) =>
...prettierConfig,
});

const getVariableName = (appName: string) => appName.replace(/[-.\/]/g, "_");
const getVariableName = (appName: string) => appName.replace(/[-.]/g, "_").replace(/\//g, "_");

// INFO: Handle stripe separately as it's an old app with different dirName than slug/appId
const getAppId = (app: { name: string }) => (app.name === "stripepayment" ? "stripe" : app.name);
Expand Down Expand Up @@ -82,7 +81,7 @@ function generateFiles() {
throw new Error(`${prefix}: ${error instanceof Error ? error.message : String(error)}`);
}
} else if (fs.existsSync(metadataPath)) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
// eslint-disable-next-line @typescript-eslint/no-require-imports
app = require(metadataPath).metadata;
} else {
app = {};
Expand Down Expand Up @@ -163,8 +162,9 @@ function generateFiles() {
function addImportStatements() {
forEachAppDir((app) => {
const chosenConfig = getChosenImportConfig(importConfig, app);
if (fileToBeImportedExists(app, chosenConfig) && chosenConfig.importName) {
const importName = chosenConfig.importName;
if (fileToBeImportedExists(app, chosenConfig)) {
// Default importName to "default" if not specified for non-lazy imports
const importName = chosenConfig.importName || "default";
if (!lazyImport) {
if (importName !== "default") {
// Import with local alias that will be used by createExportObject
Expand Down Expand Up @@ -372,9 +372,11 @@ function generateFiles() {
);
if (exportLineIndex !== -1) {
const exportLine = calendarServices[exportLineIndex];
const importStatements = calendarServices.slice(0, exportLineIndex); // Preserve import statements
const objectContent = calendarServices.slice(exportLineIndex + 1, -1); // Remove export line and closing brace

calendarOutput.push(
...importStatements, // Keep the import statements
exportLine.replace(
"export const CalendarServiceMap = {",
"export const CalendarServiceMap = process.env.NEXT_PUBLIC_IS_E2E === '1' ? {} : {"
Expand Down Expand Up @@ -461,9 +463,11 @@ function generateFiles() {
);
if (videoExportLineIndex !== -1) {
const exportLine = videoAdapters[videoExportLineIndex];
const importStatements = videoAdapters.slice(0, videoExportLineIndex); // Preserve import statements
const objectContent = videoAdapters.slice(videoExportLineIndex + 1, -1);

videoOutput.push(
...importStatements, // Keep the import statements
exportLine.replace(
"export const VideoApiAdapterMap = {",
"export const VideoApiAdapterMap = process.env.NEXT_PUBLIC_IS_E2E === '1' ? {} : {"
Expand Down
29 changes: 25 additions & 4 deletions packages/app-store/_utils/getCalendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import { CalendarServiceMap } from "../calendar.services.generated";

const log = logger.getSubLogger({ prefix: ["CalendarManager"] });

// Cache for resolved calendar services to avoid repeated dynamic imports
const calendarServiceCache = new Map<string, new (...args: unknown[]) => Calendar>();

export const getCalendar = async (
credential: CredentialForCalendarService | null
): Promise<Calendar | null> => {
Expand All @@ -25,17 +28,35 @@ export const getCalendar = async (
calendarType = calendarType.split("_crm")[0];
}

const calendarAppImportFn =
CalendarServiceMap[calendarType.split("_").join("") as keyof typeof CalendarServiceMap];
const calendarAppKey = calendarType.split("_").join("") as keyof typeof CalendarServiceMap;
const calendarAppImportFn = CalendarServiceMap[calendarAppKey];

if (!calendarAppImportFn) {
log.warn(`calendar of type ${calendarType} is not implemented`);
return null;
}

const calendarApp = await calendarAppImportFn;
// Check cache first for better performance
let CalendarService = calendarServiceCache.get(calendarAppKey);

if (!CalendarService) {
// Handle both static imports (direct class) and dynamic imports (Promise)
if (typeof calendarAppImportFn === "function") {
// Static import - direct class
CalendarService = calendarAppImportFn;
} else {
// Dynamic import - Promise that resolves to module
const calendarApp = await (calendarAppImportFn as Promise<{
default: new (...args: unknown[]) => Calendar;
}>);
CalendarService = calendarApp.default;
}

const CalendarService = calendarApp.default;
// Cache the resolved service for future use
if (CalendarService) {
calendarServiceCache.set(calendarAppKey, CalendarService);
}
}

if (!CalendarService || typeof CalendarService !== "function") {
log.warn(`calendar of type ${calendarType} is not implemented`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
calendarListMock,
} from "../__mocks__/googleapis";

import type { TFunction } from "i18next";
import { expect, test, beforeEach, vi, describe } from "vitest";
import "vitest-fetch-mock";

Expand Down Expand Up @@ -97,13 +98,17 @@ async function createDelegationCredentialForCalendarCache({
},
});

const inMemoryCredential = createInMemoryCredential({
userId: credentialInDb.userId!,
delegationCredentialId,
delegatedTo,
});

return {
...createInMemoryCredential({
userId: credentialInDb.userId!,
delegationCredentialId,
delegatedTo,
}),
...inMemoryCredential,
...credentialInDb,
// Ensure we use the DB credential's valid ID, not the in-memory one
id: credentialInDb.id,
};
}

Expand Down Expand Up @@ -517,7 +522,10 @@ describe.skip("Calendar Cache", () => {
});

// Spy on cache method that should NOT be called
const tryGetAvailabilityFromCacheSpy = vi.spyOn(calendarService, "tryGetAvailabilityFromCache" as any);
const tryGetAvailabilityFromCacheSpy = vi.spyOn(
calendarService,
"tryGetAvailabilityFromCache" as keyof typeof calendarService
);

// Spy on Google API methods that SHOULD be called
const authedCalendarSpy = vi.spyOn(calendarService, "authedCalendar");
Expand Down Expand Up @@ -557,9 +565,13 @@ describe.skip("Calendar Cache", () => {

// Mock cache to throw an error
const mockCalendarCache = {
watchCalendar: vi.fn(),
unwatchCalendar: vi.fn(),
upsertCachedAvailability: vi.fn(),
getCachedAvailability: vi.fn().mockRejectedValueOnce(new Error("Cache error")),
getCacheStatusByCredentialIds: vi.fn(),
};
vi.spyOn(CalendarCache, "init").mockResolvedValueOnce(mockCalendarCache as any);
vi.spyOn(CalendarCache, "init").mockResolvedValueOnce(mockCalendarCache as unknown as CalendarCache);

// Mock Google API response
freebusyQueryMock.mockResolvedValueOnce({
Expand Down Expand Up @@ -609,7 +621,10 @@ describe.skip("Calendar Cache", () => {
const dateTo = new Date().toISOString();

// Spy on methods that should NOT be called
const tryGetAvailabilityFromCacheSpy = vi.spyOn(calendarService, "tryGetAvailabilityFromCache" as any);
const tryGetAvailabilityFromCacheSpy = vi.spyOn(
calendarService,
"tryGetAvailabilityFromCache" as keyof typeof calendarService
);
const authedCalendarSpy = vi.spyOn(calendarService, "authedCalendar");

// Call getAvailability with only other integration calendars
Expand Down Expand Up @@ -1185,9 +1200,9 @@ describe("getAvailability", () => {
};
});
// Mock Once so that the getAvailability call doesn't accidentally reuse this mock result
freebusyQueryMock.mockImplementation(({ requestBody }: { requestBody: any }) => {
const calendarsObject: any = {};
requestBody.items.forEach((item: any, index: number) => {
freebusyQueryMock.mockImplementation(({ requestBody }: { requestBody: { items: { id: string }[] } }) => {
const calendarsObject: Record<string, { busy: { start: string; end: string }[] }> = {};
requestBody.items.forEach((item: { id: string }, index: number) => {
calendarsObject[item.id] = {
busy: mockedBusyTimes[index],
};
Expand Down Expand Up @@ -1290,7 +1305,7 @@ describe("Date Optimization Benchmarks", () => {
for (let i = 0; i < iterations; i++) {
const start = dayjs(testCase.dateFrom);
const end = dayjs(testCase.dateTo);
const diff = end.diff(start, "days");
const _diff = end.diff(start, "days");
}
const dayjsTime = performance.now() - dayjsStart;

Expand All @@ -1299,7 +1314,7 @@ describe("Date Optimization Benchmarks", () => {
for (let i = 0; i < iterations; i++) {
const start = new Date(testCase.dateFrom);
const end = new Date(testCase.dateTo);
const diff = Math.floor((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24));
const _diff = Math.floor((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24));
}
const nativeTime = performance.now() - nativeStart;

Expand Down Expand Up @@ -1437,11 +1452,18 @@ describe("Date Optimization Benchmarks", () => {

// Mock the getCacheOrFetchAvailability method to return consistent data
const getCacheOrFetchAvailabilitySpy = vi
.spyOn(calendarService as any, "getCacheOrFetchAvailability")
.spyOn(
calendarService as unknown as {
getCacheOrFetchAvailability: (...args: unknown[]) => Promise<unknown[]>;
},
"getCacheOrFetchAvailability"
)
.mockResolvedValue(mockBusyData.map((item) => ({ ...item, id: "test@calendar.com" })));

// Test single API call scenario (≤ 90 days)
const shortRangeResult = await (calendarService as any).fetchAvailabilityData(
const shortRangeResult = await (
calendarService as unknown as { fetchAvailabilityData: (...args: unknown[]) => Promise<unknown[]> }
).fetchAvailabilityData(
["test@calendar.com"],
"2024-01-01T00:00:00Z",
"2024-01-31T00:00:00Z", // 30 days
Expand All @@ -1454,7 +1476,9 @@ describe("Date Optimization Benchmarks", () => {
getCacheOrFetchAvailabilitySpy.mockClear();

// Test chunked scenario (> 90 days)
const longRangeResult = await (calendarService as any).fetchAvailabilityData(
const longRangeResult = await (
calendarService as unknown as { fetchAvailabilityData: (...args: unknown[]) => Promise<unknown[]> }
).fetchAvailabilityData(
["test@calendar.com"],
"2024-01-01T00:00:00Z",
"2024-07-01T00:00:00Z", // 182 days - should require chunking
Expand Down Expand Up @@ -1526,7 +1550,7 @@ describe("createEvent", () => {
email: "organizer@example.com",
timeZone: "UTC",
language: {
translate: (...args: any[]) => args[0], // Mock translate function
translate: ((key: string) => key) as TFunction,
locale: "en",
},
},
Expand All @@ -1537,7 +1561,7 @@ describe("createEvent", () => {
email: "attendee@example.com",
timeZone: "UTC",
language: {
translate: (...args: any[]) => args[0], // Mock translate function
translate: ((key: string) => key) as TFunction,
locale: "en",
},
},
Expand Down Expand Up @@ -1709,7 +1733,7 @@ describe("createEvent", () => {
email: "organizer@example.com",
timeZone: "UTC",
language: {
translate: (...args: any[]) => args[0], // Mock translate function
translate: ((key: string) => key) as TFunction,
locale: "en",
},
},
Expand Down
3 changes: 1 addition & 2 deletions packages/app-store/googlecalendar/lib/__tests__/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function createInMemoryCredential({
throw new Error("Test: createInMemoryCredential: delegationCredentialId is required");
}
return {
id: -1,
id: 1001,
userId,
key: {
access_token: "NOOP_UNUSED_DELEGATION_TOKEN",
Expand Down Expand Up @@ -140,7 +140,6 @@ export const createMockJWTInstance = ({
authorizeError?: { response?: { data?: { error?: string } } } | Error;
tokenExpiryDate?: number;
}) => {
console.log("createMockJWTInstance", { email, authorizeError });
const mockJWTInstance = {
type: "jwt" as const,
config: {
Expand Down
Loading
Loading