Skip to content
Merged
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
1 change: 0 additions & 1 deletion apps/api/v2/src/ee/calendars/services/calendars.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ export class CalendarsService {
eventTypeId: null,
prisma: this.dbWrite.prisma as unknown as PrismaClient,
});
console.log("saving cache", JSON.stringify(result));
await this.calendarsCacheService.setConnectedAndDestinationCalendarsCache(userId, result);

return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,7 @@ export class OAuthClientUsersController {
@Param("clientId") oAuthClientId: string,
@Body() body: CreateManagedUserInput
): Promise<CreateManagedUserOutput> {
this.logger.log(
`Creating user with data: ${JSON.stringify(body, null, 2)} for OAuth Client with ID ${oAuthClientId}`
);
this.logger.log(`Creating user for OAuth Client ${oAuthClientId}`);
const client = await this.oauthRepository.getOAuthClient(oAuthClientId);
if (!client) {
throw new NotFoundException(`OAuth Client with ID ${oAuthClientId} not found`);
Expand Down Expand Up @@ -133,7 +131,7 @@ export class OAuthClientUsersController {
@GetOrgId() organizationId: number
): Promise<GetManagedUserOutput> {
await this.validateManagedUserOwnership(clientId, userId);
this.logger.log(`Updating user with ID ${userId}: ${JSON.stringify(body, null, 2)}`);
this.logger.log(`Updating user ${userId} for OAuth Client ${clientId}`);

const user = await this.oAuthClientUsersService.updateOAuthClientUser(
clientId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,7 @@ export class OAuthClientsController {
@GetOrgId() organizationId: number,
@Body() body: CreateOAuthClientInput
): Promise<CreateOAuthClientResponseDto> {
this.logger.log(
`For organisation ${organizationId} creating OAuth Client with data: ${JSON.stringify(body)}`
);
this.logger.log(`Creating OAuth Client for organisation ${organizationId}`);

const organization = await this.teamsRepository.findByIdIncludeBilling(organizationId);
if (!organization?.platformBilling || !organization?.platformBilling?.subscriptionId) {
Expand Down Expand Up @@ -140,7 +138,7 @@ export class OAuthClientsController {
@Param("clientId") clientId: string,
@Body() body: UpdateOAuthClientInput
): Promise<GetOAuthClientResponseDto> {
this.logger.log(`For client ${clientId} updating OAuth Client with data: ${JSON.stringify(body)}`);
this.logger.log(`Updating OAuth Client ${clientId}`);
const client = await this.oAuthClientsService.updateOAuthClient(clientId, body);
return { status: SUCCESS_STATUS, data: client };
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import {
CALENDARS_QUEUE,
DEFAULT_CALENDARS_JOB,
} from "@/ee/calendars/processors/calendars.processor";
import { OrganizationsDelegationCredentialRepository } from "@/modules/organizations/delegation-credentials/organizations-delegation-credential.repository";
import { OrganizationsDelegationCredentialService } from "@/modules/organizations/delegation-credentials/services/organizations-delegation-credential.service";
import { Logger } from "@nestjs/common";
import { getQueueToken } from "@nestjs/bull";
import { Test, TestingModule } from "@nestjs/testing";

describe("OrganizationsDelegationCredentialService", () => {
let service: OrganizationsDelegationCredentialService;
let mockRepository: OrganizationsDelegationCredentialRepository;
let mockQueue: { getJob: jest.Mock; add: jest.Mock };

const orgId = 1;
const domain = "example.com";

beforeEach(async () => {
mockQueue = {
getJob: jest.fn().mockResolvedValue(null),
add: jest.fn().mockResolvedValue(undefined),
};

const module: TestingModule = await Test.createTestingModule({
providers: [
OrganizationsDelegationCredentialService,
{
provide: OrganizationsDelegationCredentialRepository,
useValue: {
findDelegatedUserProfiles: jest.fn().mockResolvedValue([]),
},
},
{
provide: getQueueToken(CALENDARS_QUEUE),
useValue: mockQueue,
},
],
}).compile();

service = module.get<OrganizationsDelegationCredentialService>(
OrganizationsDelegationCredentialService
);
mockRepository = module.get<OrganizationsDelegationCredentialRepository>(
OrganizationsDelegationCredentialRepository
);

jest.spyOn(Logger.prototype, "log").mockImplementation();
jest.spyOn(Logger.prototype, "error").mockImplementation();
});

afterEach(() => {
jest.restoreAllMocks();
});

describe("ensureDefaultCalendars", () => {
it("adds calendar jobs for each delegated user profile", async () => {
(mockRepository.findDelegatedUserProfiles as jest.Mock).mockResolvedValue([
{ userId: 1 },
{ userId: 2 },
]);

await service.ensureDefaultCalendars(orgId, domain);

expect(mockRepository.findDelegatedUserProfiles).toHaveBeenCalledWith(orgId, domain);
expect(mockQueue.add).toHaveBeenCalledTimes(2);
expect(mockQueue.add).toHaveBeenCalledWith(
DEFAULT_CALENDARS_JOB,
{ userId: 1 },
{ jobId: `${DEFAULT_CALENDARS_JOB}_1`, removeOnComplete: true }
);
expect(mockQueue.add).toHaveBeenCalledWith(
DEFAULT_CALENDARS_JOB,
{ userId: 2 },
{ jobId: `${DEFAULT_CALENDARS_JOB}_2`, removeOnComplete: true }
);
});

it("removes existing job before adding new one", async () => {
const mockExistingJob = { remove: jest.fn().mockResolvedValue(undefined) };
(mockRepository.findDelegatedUserProfiles as jest.Mock).mockResolvedValue([{ userId: 1 }]);
mockQueue.getJob.mockResolvedValue(mockExistingJob);

await service.ensureDefaultCalendars(orgId, domain);

expect(mockExistingJob.remove).toHaveBeenCalled();
expect(mockQueue.add).toHaveBeenCalledTimes(1);
});

it("skips profiles without userId", async () => {
(mockRepository.findDelegatedUserProfiles as jest.Mock).mockResolvedValue([
{ userId: 1 },
{ userId: null },
{ userId: 3 },
]);

await service.ensureDefaultCalendars(orgId, domain);

expect(mockQueue.add).toHaveBeenCalledTimes(2);
expect(mockQueue.add).toHaveBeenCalledWith(DEFAULT_CALENDARS_JOB, { userId: 1 }, expect.any(Object));
expect(mockQueue.add).toHaveBeenCalledWith(DEFAULT_CALENDARS_JOB, { userId: 3 }, expect.any(Object));
});

it("does not add jobs when profiles list is empty", async () => {
(mockRepository.findDelegatedUserProfiles as jest.Mock).mockResolvedValue([]);

await service.ensureDefaultCalendars(orgId, domain);

expect(mockQueue.add).not.toHaveBeenCalled();
});

it("processes all profiles even when some fail (Promise.allSettled)", async () => {
(mockRepository.findDelegatedUserProfiles as jest.Mock).mockResolvedValue([
{ userId: 1 },
{ userId: 2 },
{ userId: 3 },
]);
mockQueue.add
.mockResolvedValueOnce(undefined)
.mockRejectedValueOnce(new Error("Queue error"))
.mockResolvedValueOnce(undefined);

await service.ensureDefaultCalendars(orgId, domain);

// All 3 jobs were attempted despite the failure
expect(mockQueue.add).toHaveBeenCalledTimes(3);
});

it("does not throw when repository fails", async () => {
(mockRepository.findDelegatedUserProfiles as jest.Mock).mockRejectedValue(
new Error("Database error")
);

await expect(service.ensureDefaultCalendars(orgId, domain)).resolves.toBeUndefined();
expect(mockQueue.add).not.toHaveBeenCalled();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -87,23 +87,34 @@ export class OrganizationsDelegationCredentialService {
const delegatedUserProfiles =
await this.organizationsDelegationCredentialRepository.findDelegatedUserProfiles(orgId, domain);

delegatedUserProfiles.forEach(async (profile) => {
if (profile.userId) {
const job = await this.calendarsQueue.getJob(`${DEFAULT_CALENDARS_JOB}_${profile.userId}`);
if (job) {
await job.remove();
this.logger.log(`Removed default calendar job for user with id: ${profile.userId}`);
const results = await Promise.allSettled(
delegatedUserProfiles.map(async (profile) => {
if (profile.userId) {
const job = await this.calendarsQueue.getJob(`${DEFAULT_CALENDARS_JOB}_${profile.userId}`);
if (job) {
await job.remove();
this.logger.log(`Removed default calendar job for user with id: ${profile.userId}`);
}
this.logger.log(`Adding default calendar job for user with id: ${profile.userId}`);
await this.calendarsQueue.add(
DEFAULT_CALENDARS_JOB,
{
userId: profile.userId,
} satisfies DefaultCalendarsJobDataType,
{ jobId: `${DEFAULT_CALENDARS_JOB}_${profile.userId}`, removeOnComplete: true }
);
}
this.logger.log(`Adding default calendar job for user with id: ${profile.userId}`);
await this.calendarsQueue.add(
DEFAULT_CALENDARS_JOB,
{
userId: profile.userId,
} satisfies DefaultCalendarsJobDataType,
{ jobId: `${DEFAULT_CALENDARS_JOB}_${profile.userId}`, removeOnComplete: true }
);
}
});
})
);

const failures = results.filter(
(result): result is PromiseRejectedResult => result.status === "rejected"
);
if (failures.length > 0) {
this.logger.error(
`Failed to ensure default calendars for ${failures.length} users in org ${orgId}: ${failures.map((f) => f.reason).join(", ")}`
);
}
} catch (err) {
this.logger.error(
err,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,6 @@ export class TeamsEventTypesService {
});

const eventType = await this.teamsEventTypesRepository.getEventTypeById(eventTypeId);
this.logger.debug("nl debug - update team event type - eventType", JSON.stringify(eventType, null, 2));

if (!eventType) {
throw new NotFoundException(`Event type with id ${eventTypeId} not found`);
Expand Down
2 changes: 1 addition & 1 deletion apps/web/playwright/availability.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ test.describe("Availability", () => {
await submitAndWaitForResponse(page, "/api/trpc/availability/schedule.delete?batch=1", {
action: () => page.locator('[data-testid="delete-schedule"]').click(),
});
await expect(page.locator('[data-testid="schedules"] > li').nth(1)).toHaveCount(0);
await expect(page.locator('[data-testid="schedules"] > li').nth(1)).toHaveCount(0, { timeout: 0 });
});
await test.step("Cannot delete the last schedule", async () => {
await page.locator('[data-testid="schedules"] > li').nth(0).getByTestId("schedule-more").click();
Expand Down
16 changes: 8 additions & 8 deletions apps/web/playwright/booking-seats.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,14 @@ test.describe("Booking with Seats", () => {
await test.step("Attendee #2 shouldn't be able to cancel booking using only booking/uid", async () => {
await page.goto(`/booking/${booking.uid}`);

await expect(page.locator("[text=Cancel]")).toHaveCount(0);
await expect(page.locator("[text=Cancel]")).toHaveCount(0, { timeout: 0 });
});

await test.step("Attendee #2 shouldn't be able to cancel booking using randomString for seatReferenceUId", async () => {
await page.goto(`/booking/${booking.uid}?seatReferenceUid=${randomString(10)}`);

// expect cancel button to don't be in the page
await expect(page.locator("[text=Cancel]")).toHaveCount(0);
await expect(page.locator("[text=Cancel]")).toHaveCount(0, { timeout: 0 });
});
});

Expand All @@ -82,7 +82,7 @@ test.describe("Booking with Seats", () => {
{ name: "John Third", email: "third+seats@cal.com", timeZone: "Europe/Berlin" },
]);
await page.goto(`/booking/${booking.uid}?cancel=true`);
await expect(page.locator("[text=Cancel]")).toHaveCount(0);
await expect(page.locator("[text=Cancel]")).toHaveCount(0, { timeout: 0 });

// expect login text to be in the page, not data-testid
await expect(page.locator("text=Login")).toHaveCount(1);
Expand All @@ -99,7 +99,7 @@ test.describe("Booking with Seats", () => {
await page.goto(`/booking/${booking.uid}?cancel=true`);

// expect login button to don't be in the page
await expect(page.locator("text=Login")).toHaveCount(0);
await expect(page.locator("text=Login")).toHaveCount(0, { timeout: 0 });

// fill reason for cancellation
await page.fill('[data-testid="cancel_reason"]', "Double booked!");
Expand Down Expand Up @@ -183,7 +183,7 @@ test.describe("Reschedule for booking with seats", () => {
await page.locator('[data-testid="day"][data-disabled="false"]').nth(1).click();

// Validate that the number of seats its 10
await expect(page.locator("text=9 / 10 Seats available")).toHaveCount(0);
await expect(page.locator("text=9 / 10 Seats available")).toHaveCount(0, { timeout: 0 });
});

test("Should cancel with seats but event should be still accessible and with one less attendee/seat", async ({
Expand Down Expand Up @@ -325,7 +325,7 @@ test.describe("Reschedule for booking with seats", () => {
// No attendees should be displayed only the one that it's cancelling
const notFoundSecondAttendee = await page.locator('p[data-testid="attendee-email-second+seats@cal.com"]');

await expect(notFoundSecondAttendee).toHaveCount(0);
await expect(notFoundSecondAttendee).toHaveCount(0, { timeout: 0 });
const foundFirstAttendee = await page.locator('p[data-testid="attendee-email-first+seats@cal.com"]');
await expect(foundFirstAttendee).toHaveCount(1);

Expand Down Expand Up @@ -365,7 +365,7 @@ test.describe("Reschedule for booking with seats", () => {
const getBooking = await booking.self();

await page.goto(`/booking/${booking.uid}`);
await expect(page.locator('[data-testid="reschedule"]')).toHaveCount(0);
await expect(page.locator('[data-testid="reschedule"]')).toHaveCount(0, { timeout: 0 });

// expect login text to be in the page, not data-testid
await expect(page.locator("text=Login")).toHaveCount(1);
Expand All @@ -382,7 +382,7 @@ test.describe("Reschedule for booking with seats", () => {
await page.goto(`/booking/${booking.uid}`);

// expect login button to don't be in the page
await expect(page.locator("text=Login")).toHaveCount(0);
await expect(page.locator("text=Login")).toHaveCount(0, { timeout: 0 });

// reschedule-link click
await page.locator('[data-testid="reschedule-link"]').click();
Expand Down
2 changes: 1 addition & 1 deletion apps/web/playwright/bookings-list.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ test.describe("Bookings", () => {
.click();
await bookingsGetResponse2;

await expect(page.locator('[data-testid="booking-item"]')).toHaveCount(0);
await expect(page.locator('[data-testid="booking-item"]')).toHaveCount(0, { timeout: 0 });
});

test.describe("Filter Dropdown Item Search", () => {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/playwright/insights-charts.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ test.describe("Insights > Charts Loading", () => {

// Verify no charts are in error state
const errorCharts = page.locator('[data-testid="chart-card"][data-loading-state="error"]');
await expect(errorCharts).toHaveCount(0);
await expect(errorCharts).toHaveCount(0, { timeout: 0 });
});
});
});
Loading
Loading