fix: update handler to not delete banner (#24106)
* Fix update handler to not delete banner * code cleanup * add banner-upload.e2e * fix: add slug fallback in banner-upload e2e test - Use org.slug as fallback when requestedSlug is undefined - Addresses GitHub comment about guarding against undefined slug - Prevents test failures when requestedSlug is not set - Fix ESLint warnings: remove unnecessary escape characters and unused variable Co-Authored-By: sean@cal.com <Sean@brydon.io> * test: add banner preservation test for profile updates - Verify that updating organization profile without banner changes preserves existing banner - Ensures update handler doesn't overwrite banner when undefined is passed - Addresses user request to test banner preservation during profile saves Co-Authored-By: sean@cal.com <Sean@brydon.io> * remove banner e2e wip --------- Co-authored-by: Hariom Balhara <hariombalhara@gmail.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
sean@cal.com
Hariom Balhara
Devin AI
parent
cc8a1290ee
commit
f6b061f787
@@ -0,0 +1,118 @@
|
||||
import { describe, expect, it, vi, beforeEach } from "vitest";
|
||||
|
||||
import { getBannerUrl } from "./update.handler";
|
||||
|
||||
vi.mock("@calcom/lib/server/avatar", () => ({
|
||||
uploadLogo: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/lib/server/resizeBase64Image", () => ({
|
||||
resizeBase64Image: vi.fn((input) => input),
|
||||
}));
|
||||
|
||||
const { uploadLogo } = await import("@calcom/lib/server/avatar");
|
||||
const { resizeBase64Image } = await import("@calcom/lib/server/resizeBase64Image");
|
||||
|
||||
describe("getBannerUrl", () => {
|
||||
const mockTeamId = 123;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("should return undefined when banner is undefined (not provided)", async () => {
|
||||
const result = await getBannerUrl(undefined, mockTeamId);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
expect(uploadLogo).not.toHaveBeenCalled();
|
||||
expect(resizeBase64Image).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should return null when banner is explicitly set to null", async () => {
|
||||
const result = await getBannerUrl(null, mockTeamId);
|
||||
|
||||
expect(result).toBe(null);
|
||||
expect(uploadLogo).not.toHaveBeenCalled();
|
||||
expect(resizeBase64Image).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should upload and return URL for valid PNG base64 banner", async () => {
|
||||
const mockBannerUrl = "https://example.com/banner.png";
|
||||
const validBase64 =
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==";
|
||||
|
||||
vi.mocked(uploadLogo).mockResolvedValue(mockBannerUrl);
|
||||
|
||||
const result = await getBannerUrl(validBase64, mockTeamId);
|
||||
|
||||
expect(result).toBe(mockBannerUrl);
|
||||
expect(resizeBase64Image).toHaveBeenCalledWith(validBase64, { maxSize: 1500 });
|
||||
expect(uploadLogo).toHaveBeenCalledWith({
|
||||
logo: validBase64, // resizeBase64Image mock returns input unchanged
|
||||
teamId: mockTeamId,
|
||||
isBanner: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("should upload and return URL for valid JPEG base64 banner", async () => {
|
||||
const mockBannerUrl = "https://example.com/banner.jpg";
|
||||
const validBase64 =
|
||||
"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwA/wA==";
|
||||
|
||||
vi.mocked(uploadLogo).mockResolvedValue(mockBannerUrl);
|
||||
|
||||
const result = await getBannerUrl(validBase64, mockTeamId);
|
||||
|
||||
expect(result).toBe(mockBannerUrl);
|
||||
expect(resizeBase64Image).toHaveBeenCalledWith(validBase64, { maxSize: 1500 });
|
||||
expect(uploadLogo).toHaveBeenCalledWith({
|
||||
logo: validBase64,
|
||||
teamId: mockTeamId,
|
||||
isBanner: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("should upload and return URL for valid JPG base64 banner", async () => {
|
||||
const mockBannerUrl = "https://example.com/banner.jpg";
|
||||
const validBase64 =
|
||||
"data:image/jpg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwA/wA==";
|
||||
|
||||
vi.mocked(uploadLogo).mockResolvedValue(mockBannerUrl);
|
||||
|
||||
const result = await getBannerUrl(validBase64, mockTeamId);
|
||||
|
||||
expect(result).toBe(mockBannerUrl);
|
||||
expect(resizeBase64Image).toHaveBeenCalledWith(validBase64, { maxSize: 1500 });
|
||||
expect(uploadLogo).toHaveBeenCalledWith({
|
||||
logo: validBase64,
|
||||
teamId: mockTeamId,
|
||||
isBanner: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("should return undefined for invalid banner string", async () => {
|
||||
const result = await getBannerUrl("invalid-banner-string", mockTeamId);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
expect(uploadLogo).not.toHaveBeenCalled();
|
||||
expect(resizeBase64Image).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should return undefined for empty banner string", async () => {
|
||||
const result = await getBannerUrl("", mockTeamId);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
expect(uploadLogo).not.toHaveBeenCalled();
|
||||
expect(resizeBase64Image).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should return undefined for non-image base64 string", async () => {
|
||||
const invalidBase64 = "data:text/plain;base64,SGVsbG8gV29ybGQ=";
|
||||
|
||||
const result = await getBannerUrl(invalidBase64, mockTeamId);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
expect(uploadLogo).not.toHaveBeenCalled();
|
||||
expect(resizeBase64Image).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -15,6 +15,38 @@ import { TRPCError } from "@trpc/server";
|
||||
import type { TrpcSessionUser } from "../../../types";
|
||||
import type { TUpdateInputSchema } from "./update.schema";
|
||||
|
||||
export const getBannerUrl = async (
|
||||
banner: string | null | undefined,
|
||||
teamId: number
|
||||
): Promise<string | null | undefined> => {
|
||||
if (banner === undefined) {
|
||||
// Banner not provided, don't update
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (banner === null) {
|
||||
// Explicitly set to null, remove banner
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
banner.startsWith("data:image/png;base64,") ||
|
||||
banner.startsWith("data:image/jpeg;base64,") ||
|
||||
banner.startsWith("data:image/jpg;base64,")
|
||||
) {
|
||||
// Valid base64 image, resize and upload
|
||||
const resizedBanner = await resizeBase64Image(banner, { maxSize: 1500 });
|
||||
return await uploadLogo({
|
||||
logo: resizedBanner,
|
||||
teamId,
|
||||
isBanner: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Invalid banner string, don't update
|
||||
return undefined;
|
||||
};
|
||||
|
||||
type UpdateOptions = {
|
||||
ctx: {
|
||||
user: NonNullable<TrpcSessionUser>;
|
||||
@@ -185,21 +217,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,"))
|
||||
) {
|
||||
const banner = await resizeBase64Image(input.banner, { maxSize: 1500 });
|
||||
data.bannerUrl = await uploadLogo({
|
||||
logo: banner,
|
||||
teamId: currentOrgId,
|
||||
isBanner: true,
|
||||
});
|
||||
} else {
|
||||
data.bannerUrl = null;
|
||||
}
|
||||
data.bannerUrl = await getBannerUrl(input.banner, currentOrgId);
|
||||
|
||||
if (
|
||||
input.logoUrl &&
|
||||
|
||||
Reference in New Issue
Block a user