chore: add test for updateProfilePhotoGoogle (#22169)
* fix: process base64 avatar image * better name * more * fix import * add test * util * clean up * address
This commit is contained in:
1 parent
2fe71c3695
commit
67032ddaf1
8 files changed
+139
-41
No files matched your search
@@ -0,0 +1,114 @@
|
||||
import { oauth2_v2 } from "@googleapis/oauth2";
|
||||
import type { OAuth2Client } from "googleapis-common";
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { uploadAvatar } from "@calcom/lib/server/avatar";
|
||||
import { UserRepository } from "@calcom/lib/server/repository/user";
|
||||
import { resizeBase64Image } from "@calcom/lib/server/resizeBase64Image";
|
||||
|
||||
import { updateProfilePhotoGoogle } from "./updateProfilePhotoGoogle";
|
||||
|
||||
vi.mock("@googleapis/oauth2", () => ({
|
||||
oauth2_v2: {
|
||||
Oauth2: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/lib/logger", () => ({
|
||||
default: {
|
||||
error: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/lib/server/avatar", () => ({
|
||||
uploadAvatar: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/lib/server/resizeBase64Image", () => ({
|
||||
resizeBase64Image: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/lib/server/repository/user", () => ({
|
||||
UserRepository: {
|
||||
updateAvatar: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe("updateProfilePhotoGoogle", () => {
|
||||
const mockOAuth2Client = {} as OAuth2Client;
|
||||
const userId = 123;
|
||||
|
||||
let mockOauth2Instance: any;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
mockOauth2Instance = {
|
||||
userinfo: {
|
||||
get: vi.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
(oauth2_v2.Oauth2 as any).mockImplementation(() => mockOauth2Instance);
|
||||
(uploadAvatar as any).mockResolvedValue("/api/avatar/processed-123.png");
|
||||
(resizeBase64Image as any).mockResolvedValue("data:image/png;base64,resized-data");
|
||||
(UserRepository.updateAvatar as any).mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
it("should update avatar with valid URL", async () => {
|
||||
const validUrl = "https://example.com/avatar.jpg";
|
||||
mockOauth2Instance.userinfo.get.mockResolvedValue({
|
||||
data: { picture: validUrl },
|
||||
});
|
||||
|
||||
await updateProfilePhotoGoogle(mockOAuth2Client, userId);
|
||||
|
||||
expect(UserRepository.updateAvatar).toHaveBeenCalledWith({
|
||||
id: userId,
|
||||
avatarUrl: validUrl,
|
||||
});
|
||||
expect(uploadAvatar).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should process base64 data", async () => {
|
||||
const base64Data =
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=";
|
||||
mockOauth2Instance.userinfo.get.mockResolvedValue({
|
||||
data: { picture: base64Data },
|
||||
});
|
||||
|
||||
await updateProfilePhotoGoogle(mockOAuth2Client, userId);
|
||||
|
||||
expect(resizeBase64Image).toHaveBeenCalledWith(base64Data);
|
||||
expect(uploadAvatar).toHaveBeenCalledWith({
|
||||
avatar: "data:image/png;base64,resized-data",
|
||||
userId,
|
||||
});
|
||||
expect(UserRepository.updateAvatar).toHaveBeenCalledWith({
|
||||
id: userId,
|
||||
avatarUrl: "/api/avatar/processed-123.png",
|
||||
});
|
||||
});
|
||||
|
||||
it("should do nothing when no picture provided", async () => {
|
||||
mockOauth2Instance.userinfo.get.mockResolvedValue({
|
||||
data: {},
|
||||
});
|
||||
|
||||
await updateProfilePhotoGoogle(mockOAuth2Client, userId);
|
||||
|
||||
expect(UserRepository.updateAvatar).not.toHaveBeenCalled();
|
||||
expect(uploadAvatar).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should handle errors gracefully", async () => {
|
||||
const error = new Error("OAuth API failed");
|
||||
mockOauth2Instance.userinfo.get.mockRejectedValue(error);
|
||||
|
||||
await updateProfilePhotoGoogle(mockOAuth2Client, userId);
|
||||
|
||||
expect(logger.error).toHaveBeenCalledWith("Error updating avatarUrl from google calendar connect", error);
|
||||
expect(UserRepository.updateAvatar).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
import { oauth2_v2 } from "@googleapis/oauth2";
|
||||
import type { OAuth2Client } from "googleapis-common";
|
||||
|
||||
import { isBase64Image } from "@calcom/lib/isBase64Image";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { uploadAvatar } from "@calcom/lib/server/avatar";
|
||||
import { UserRepository } from "@calcom/lib/server/repository/user";
|
||||
@@ -16,11 +17,7 @@ export async function updateProfilePhotoGoogle(oAuth2Client: OAuth2Client, userI
|
||||
}
|
||||
|
||||
// Handle base64 data
|
||||
if (
|
||||
avatarUrl.startsWith("data:image/png;base64,") ||
|
||||
avatarUrl.startsWith("data:image/jpeg;base64,") ||
|
||||
avatarUrl.startsWith("data:image/jpg;base64,")
|
||||
) {
|
||||
if (isBase64Image(avatarUrl)) {
|
||||
const resizedAvatarUrl = await uploadAvatar({
|
||||
avatar: await resizeBase64Image(avatarUrl),
|
||||
userId,
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Checks if a string is a supported base64 image format
|
||||
*/
|
||||
export function isBase64Image(input: string | null | undefined): boolean {
|
||||
if (!input) return false;
|
||||
|
||||
return (
|
||||
input.startsWith("data:image/png;base64,") ||
|
||||
input.startsWith("data:image/jpeg;base64,") ||
|
||||
input.startsWith("data:image/jpg;base64,")
|
||||
);
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import { StripeBillingService } from "@calcom/features/ee/billing/stripe-billlin
|
||||
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
|
||||
import hasKeyInMetadata from "@calcom/lib/hasKeyInMetadata";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { isBase64Image } from "@calcom/lib/isBase64Image";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { uploadAvatar } from "@calcom/lib/server/avatar";
|
||||
import { checkUsername } from "@calcom/lib/server/checkUsername";
|
||||
@@ -152,12 +153,7 @@ export const updateProfileHandler = async ({ ctx, input }: UpdateProfileOptions)
|
||||
}
|
||||
|
||||
// if defined AND a base 64 string, upload and update the avatar URL
|
||||
if (
|
||||
input.avatarUrl &&
|
||||
(input.avatarUrl.startsWith("data:image/png;base64,") ||
|
||||
input.avatarUrl.startsWith("data:image/jpeg;base64,") ||
|
||||
input.avatarUrl.startsWith("data:image/jpg;base64,"))
|
||||
) {
|
||||
if (input.avatarUrl && isBase64Image(input.avatarUrl)) {
|
||||
data.avatarUrl = await uploadAvatar({
|
||||
avatar: await resizeBase64Image(input.avatarUrl),
|
||||
userId: user.id,
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { Prisma } from "@prisma/client";
|
||||
|
||||
import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants";
|
||||
import { getMetadataHelpers } from "@calcom/lib/getMetadataHelpers";
|
||||
import { isBase64Image } from "@calcom/lib/isBase64Image";
|
||||
import { uploadLogo } from "@calcom/lib/server/avatar";
|
||||
import { isOrganisationAdmin } from "@calcom/lib/server/queries/organisations";
|
||||
import { resizeBase64Image } from "@calcom/lib/server/resizeBase64Image";
|
||||
@@ -161,12 +162,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||
metadata: mergeMetadata({ ...input.metadata }),
|
||||
};
|
||||
|
||||
if (
|
||||
input.banner &&
|
||||
(input.banner.startsWith("data:image/png;base64,") ||
|
||||
input.banner.startsWith("data:image/jpeg;base64,") ||
|
||||
input.banner.startsWith("data:image/jpg;base64,"))
|
||||
) {
|
||||
if (input.banner && isBase64Image(input.banner)) {
|
||||
const banner = await resizeBase64Image(input.banner, { maxSize: 1500 });
|
||||
data.bannerUrl = await uploadLogo({
|
||||
logo: banner,
|
||||
@@ -177,12 +173,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||
data.bannerUrl = null;
|
||||
}
|
||||
|
||||
if (
|
||||
input.logoUrl &&
|
||||
(input.logoUrl.startsWith("data:image/png;base64,") ||
|
||||
input.logoUrl.startsWith("data:image/jpeg;base64,") ||
|
||||
input.logoUrl.startsWith("data:image/jpg;base64,"))
|
||||
) {
|
||||
if (input.logoUrl && isBase64Image(input.logoUrl)) {
|
||||
data.logoUrl = await uploadLogo({
|
||||
logo: await resizeBase64Image(input.logoUrl),
|
||||
teamId: currentOrgId,
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { Prisma, PrismaPromise, User, Membership, Profile } from "@prisma/c
|
||||
|
||||
import { ensureOrganizationIsReviewed } from "@calcom/ee/organizations/lib/ensureOrganizationIsReviewed";
|
||||
import { checkAdminOrOwner } from "@calcom/features/auth/lib/checkAdminOrOwner";
|
||||
import { isBase64Image } from "@calcom/lib/isBase64Image";
|
||||
import { uploadAvatar } from "@calcom/lib/server/avatar";
|
||||
import { checkRegularUsername } from "@calcom/lib/server/checkRegularUsername";
|
||||
import { isOrganisationAdmin, isOrganisationOwner } from "@calcom/lib/server/queries/organisations";
|
||||
@@ -119,12 +120,7 @@ export const updateUserHandler = async ({ ctx, input }: UpdateUserOptions) => {
|
||||
timeZone: input.timeZone,
|
||||
};
|
||||
|
||||
if (
|
||||
input.avatar &&
|
||||
(input.avatar.startsWith("data:image/png;base64,") ||
|
||||
input.avatar.startsWith("data:image/jpeg;base64,") ||
|
||||
input.avatar.startsWith("data:image/jpg;base64,"))
|
||||
) {
|
||||
if (input.avatar && isBase64Image(input.avatar)) {
|
||||
const avatar = await resizeBase64Image(input.avatar);
|
||||
data.avatarUrl = await uploadAvatar({
|
||||
avatar,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { generateTeamCheckoutSession } from "@calcom/features/ee/teams/lib/payments";
|
||||
import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { isBase64Image } from "@calcom/lib/isBase64Image";
|
||||
import { uploadLogo } from "@calcom/lib/server/avatar";
|
||||
import { ProfileRepository } from "@calcom/lib/server/repository/profile";
|
||||
import { resizeBase64Image } from "@calcom/lib/server/resizeBase64Image";
|
||||
@@ -106,12 +107,7 @@ export const createHandler = async ({ ctx, input }: CreateOptions) => {
|
||||
},
|
||||
});
|
||||
// Upload logo, create doesn't allow logo removal
|
||||
if (
|
||||
input.logo &&
|
||||
(input.logo.startsWith("data:image/png;base64,") ||
|
||||
input.logo.startsWith("data:image/jpeg;base64,") ||
|
||||
input.logo.startsWith("data:image/jpg;base64,"))
|
||||
) {
|
||||
if (input.logo && isBase64Image(input.logo)) {
|
||||
const logoUrl = await uploadLogo({
|
||||
logo: await resizeBase64Image(input.logo),
|
||||
teamId: createdTeam.id,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { getOrgFullOrigin } from "@calcom/ee/organizations/lib/orgDomains";
|
||||
import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants";
|
||||
import type { IntervalLimit } from "@calcom/lib/intervalLimits/intervalLimitSchema";
|
||||
import { validateIntervalLimitOrder } from "@calcom/lib/intervalLimits/validateIntervalLimitOrder";
|
||||
import { isBase64Image } from "@calcom/lib/isBase64Image";
|
||||
import { uploadLogo } from "@calcom/lib/server/avatar";
|
||||
import { isTeamAdmin } from "@calcom/lib/server/queries/teams";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
@@ -70,12 +71,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||
rrTimestampBasis: input.rrTimestampBasis,
|
||||
};
|
||||
|
||||
if (
|
||||
input.logo &&
|
||||
(input.logo.startsWith("data:image/png;base64,") ||
|
||||
input.logo.startsWith("data:image/jpeg;base64,") ||
|
||||
input.logo.startsWith("data:image/jpg;base64,"))
|
||||
) {
|
||||
if (input.logo && isBase64Image(input.logo)) {
|
||||
data.logoUrl = await uploadLogo({ teamId: input.id, logo: input.logo });
|
||||
} else if (typeof input.logo !== "undefined" && !input.logo) {
|
||||
data.logoUrl = null;
|
||||
|
||||
Reference in new issue
Block a user