From 672161f9d156ee3065f30a7a936a105ba2f26f21 Mon Sep 17 00:00:00 2001 From: reneshen0328 Date: Tue, 10 Feb 2026 15:44:58 -0800 Subject: [PATCH] fix(content-sharing): Fix create shared link bug --- .../__tests__/sharingService.test.ts | 92 ++++++++++++++++--- .../hooks/__tests__/useSharingService.test.ts | 38 +++++++- .../hooks/useSharingService.ts | 9 +- .../content-sharing/sharingService.ts | 18 ++++ 4 files changed, 133 insertions(+), 24 deletions(-) diff --git a/src/elements/content-sharing/__tests__/sharingService.test.ts b/src/elements/content-sharing/__tests__/sharingService.test.ts index cf8181fea3..9b2de9c501 100644 --- a/src/elements/content-sharing/__tests__/sharingService.test.ts +++ b/src/elements/content-sharing/__tests__/sharingService.test.ts @@ -13,8 +13,9 @@ const options = { id: '123', permissions: { can_set_share_access: true, can_shar const mockOnUpdateSharedLink = jest.fn(); const mockOnRemoveSharedLink = jest.fn(); -const createSharingServiceWrapper = () => { +const createSharingServiceWrapper = (hasSharedLink = true) => { return createSharingService({ + hasSharedLink, itemApiInstance: mockItemApiInstance, onUpdateSharedLink: mockOnUpdateSharedLink, onRemoveSharedLink: mockOnRemoveSharedLink, @@ -63,6 +64,18 @@ describe('elements/content-sharing/sharingService', () => { CONTENT_SHARING_SHARED_LINK_UPDATE_PARAMS, ); }); + + test('should reject with 404 error when hasSharedLink is false', async () => { + const service = createSharingServiceWrapper(false); + + await expect(service.changeSharedLinkPermission(PERMISSION_CAN_DOWNLOAD)).rejects.toEqual( + expect.objectContaining({ + message: 'Shared link not found', + status: 404, + }), + ); + expect(mockItemApiInstance.updateSharedLink).not.toHaveBeenCalled(); + }); }); describe('changeSharedLinkAccess', () => { @@ -87,6 +100,18 @@ describe('elements/content-sharing/sharingService', () => { ); }, ); + + test('should reject with 404 error when hasSharedLink is false', async () => { + const service = createSharingServiceWrapper(false); + + await expect(service.changeSharedLinkAccess('open')).rejects.toEqual( + expect.objectContaining({ + message: 'Shared link not found', + status: 404, + }), + ); + expect(mockItemApiInstance.share).not.toHaveBeenCalled(); + }); }); describe('createSharedLink', () => { @@ -96,22 +121,25 @@ describe('elements/content-sharing/sharingService', () => { expect(typeof service.createSharedLink).toBe('function'); }); - test('should call share with correct parameters when createSharedLink is called', async () => { - mockItemApiInstance.share.mockImplementation((_options, _access, successCallback) => { - successCallback({ id: '123', shared_link: null }); - }); + test.each([true, false])( + 'should call share with correct parameters when createSharedLink is called', + async hasSharedLink => { + mockItemApiInstance.share.mockImplementation((_options, _access, successCallback) => { + successCallback({ id: '123', shared_link: null }); + }); - const service = createSharingServiceWrapper(); - await service.createSharedLink(); + const service = createSharingServiceWrapper(hasSharedLink); + await service.createSharedLink(); - expect(mockItemApiInstance.share).toHaveBeenCalledWith( - options, - undefined, - expect.any(Function), - expect.any(Function), - CONTENT_SHARING_SHARED_LINK_UPDATE_PARAMS, - ); - }); + expect(mockItemApiInstance.share).toHaveBeenCalledWith( + options, + undefined, + expect.any(Function), + expect.any(Function), + CONTENT_SHARING_SHARED_LINK_UPDATE_PARAMS, + ); + }, + ); }); describe('deleteSharedLink', () => { @@ -137,6 +165,18 @@ describe('elements/content-sharing/sharingService', () => { CONTENT_SHARING_SHARED_LINK_UPDATE_PARAMS, ); }); + + test('should reject with 404 error when hasSharedLink is false', async () => { + const service = createSharingServiceWrapper(false); + + await expect(service.deleteSharedLink()).rejects.toEqual( + expect.objectContaining({ + message: 'Shared link not found', + status: 404, + }), + ); + expect(mockItemApiInstance.share).not.toHaveBeenCalled(); + }); }); describe('updateSharedLink', () => { @@ -200,6 +240,7 @@ describe('elements/content-sharing/sharingService', () => { (convertSharedLinkSettings as jest.Mock).mockReturnValue(mockConvertedSharedLinkSettings); const service = createSharingService({ + hasSharedLink: true, itemApiInstance: mockItemApiInstance, onUpdateSharedLink: mockOnUpdateSharedLink, onRemoveSharedLink: mockOnRemoveSharedLink, @@ -258,6 +299,27 @@ describe('elements/content-sharing/sharingService', () => { expect(convertSharedLinkSettings).toHaveBeenCalledWith(sharedLinkSettings, undefined, undefined, undefined); }); + + test('should reject with 404 error when hasSharedLink is false', async () => { + const service = createSharingServiceWrapper(false); + + const sharedLinkSettings = { + expiration: null, + isDownloadEnabled: true, + isExpirationEnabled: false, + isPasswordEnabled: false, + password: '', + vanityName: 'vanity-name', + }; + + await expect(service.updateSharedLink(sharedLinkSettings)).rejects.toEqual( + expect.objectContaining({ + message: 'Shared link not found', + status: 404, + }), + ); + expect(mockItemApiInstance.updateSharedLink).not.toHaveBeenCalled(); + }); }); describe('error handling', () => { diff --git a/src/elements/content-sharing/hooks/__tests__/useSharingService.test.ts b/src/elements/content-sharing/hooks/__tests__/useSharingService.test.ts index b9e203878a..b0469f6fd9 100644 --- a/src/elements/content-sharing/hooks/__tests__/useSharingService.test.ts +++ b/src/elements/content-sharing/hooks/__tests__/useSharingService.test.ts @@ -106,13 +106,24 @@ describe('elements/content-sharing/hooks/useSharingService', () => { expect(createSharingService).not.toHaveBeenCalled(); }); - test('should return null itemApiInstance and sharingService when sharedLink is null', () => { + test('should create sharingService when sharedLink is null', () => { + mockApi.getFileAPI.mockReturnValue({}); const { result } = renderHookWithProps({ sharedLink: null }); - expect(result.current.sharingService).toEqual({ sendInvitations: expect.any(Function) }); - expect(mockApi.getFileAPI).not.toHaveBeenCalled(); - expect(mockApi.getFolderAPI).not.toHaveBeenCalled(); - expect(createSharingService).not.toHaveBeenCalled(); + expect(result.current.sharingService).toEqual({ + ...mockSharingService, + sendInvitations: expect.any(Function), + }); + expect(mockApi.getFileAPI).toHaveBeenCalled(); + expect(createSharingService).toHaveBeenCalledWith( + expect.objectContaining({ + hasSharedLink: false, + options: expect.objectContaining({ + access: undefined, + isDownloadAvailable: false, + }), + }), + ); }); test('should return null itemApiInstance and sharingService when itemType is neither TYPE_FILE nor TYPE_FOLDER', () => { @@ -124,6 +135,21 @@ describe('elements/content-sharing/hooks/useSharingService', () => { expect(createSharingService).not.toHaveBeenCalled(); }); + test('should create sharingService with hasSharedLink true when sharedLink has url', () => { + mockApi.getFileAPI.mockReturnValue({}); + const sharedLinkWithUrl = { + ...mockSharedLink, + url: 'https://example.com/shared-link', + }; + renderHookWithProps({ sharedLink: sharedLinkWithUrl }); + + expect(createSharingService).toHaveBeenCalledWith( + expect.objectContaining({ + hasSharedLink: true, + }), + ); + }); + describe('when itemType is TYPE_FILE', () => { beforeEach(() => { mockApi.getFileAPI.mockReturnValue({}); @@ -140,6 +166,7 @@ describe('elements/content-sharing/hooks/useSharingService', () => { sendInvitations: expect.any(Function), }); expect(createSharingService).toHaveBeenCalledWith({ + hasSharedLink: false, itemApiInstance: {}, onUpdateSharedLink: expect.any(Function), onRemoveSharedLink: expect.any(Function), @@ -196,6 +223,7 @@ describe('elements/content-sharing/hooks/useSharingService', () => { sendInvitations: expect.any(Function), }); expect(createSharingService).toHaveBeenCalledWith({ + hasSharedLink: false, itemApiInstance: {}, onUpdateSharedLink: expect.any(Function), onRemoveSharedLink: expect.any(Function), diff --git a/src/elements/content-sharing/hooks/useSharingService.ts b/src/elements/content-sharing/hooks/useSharingService.ts index 5c8118b09a..f661f8578f 100644 --- a/src/elements/content-sharing/hooks/useSharingService.ts +++ b/src/elements/content-sharing/hooks/useSharingService.ts @@ -26,7 +26,7 @@ export const useSharingService = ({ // itemApiInstance should only be called once or the API will cause an issue where it gets cancelled const itemApiInstance = React.useMemo(() => { - if (!item || !sharedLink) { + if (!item) { return null; } @@ -39,7 +39,7 @@ export const useSharingService = ({ } return null; - }, [api, item, itemType, sharedLink]); + }, [api, item, itemType]); const sharingService = React.useMemo(() => { if (!itemApiInstance) { @@ -48,13 +48,13 @@ export const useSharingService = ({ const options = { id: itemId, - access: sharedLink.access, + access: sharedLink?.access, permissions: { can_set_share_access: sharingServiceProps?.can_set_share_access, can_share: sharingServiceProps?.can_share, }, serverUrl: sharingServiceProps?.serverUrl, - isDownloadAvailable: sharedLink.settings?.isDownloadAvailable ?? false, + isDownloadAvailable: sharedLink?.settings?.isDownloadAvailable ?? false, }; const handleUpdateSharedLink = updatedItemData => { @@ -70,6 +70,7 @@ export const useSharingService = ({ }; return createSharingService({ + hasSharedLink: !!sharedLink?.url, itemApiInstance, onUpdateSharedLink: handleUpdateSharedLink, onRemoveSharedLink: handleRemoveSharedLink, diff --git a/src/elements/content-sharing/sharingService.ts b/src/elements/content-sharing/sharingService.ts index 683c394405..f468354af9 100644 --- a/src/elements/content-sharing/sharingService.ts +++ b/src/elements/content-sharing/sharingService.ts @@ -20,6 +20,7 @@ export interface Options extends ItemData { } export interface CreateSharingServiceArgs { + hasSharedLink: boolean; itemApiInstance: API; onUpdateSharedLink: (itemData: ItemData) => void; onRemoveSharedLink: (itemData: ItemData) => void; @@ -27,6 +28,7 @@ export interface CreateSharingServiceArgs { } export const createSharingService = ({ + hasSharedLink, itemApiInstance, onUpdateSharedLink, onRemoveSharedLink, @@ -35,6 +37,10 @@ export const createSharingService = ({ const { id, permissions } = options; const changeSharedLinkAccess = async (access: string): Promise => { + if (!hasSharedLink) { + return Promise.reject(Object.assign(new Error('Shared link not found'), { status: 404 })); + } + return itemApiInstance.share( { id, permissions }, access, @@ -45,6 +51,10 @@ export const createSharingService = ({ }; const changeSharedLinkPermission = async (permissionLevel: string): Promise => { + if (!hasSharedLink) { + return Promise.reject(Object.assign(new Error('Shared link not found'), { status: 404 })); + } + return itemApiInstance.updateSharedLink( { id, permissions }, { permissions: convertSharedLinkPermissions(permissionLevel) }, @@ -55,6 +65,10 @@ export const createSharingService = ({ }; const updateSharedLink = async (sharedLinkSettings: SharedLinkSettings) => { + if (!hasSharedLink) { + return Promise.reject(Object.assign(new Error('Shared link not found'), { status: 404 })); + } + const { access, isDownloadAvailable, serverUrl } = options; return new Promise((resolve, reject) => { @@ -91,6 +105,10 @@ export const createSharingService = ({ }; const deleteSharedLink = async () => { + if (!hasSharedLink) { + return Promise.reject(Object.assign(new Error('Shared link not found'), { status: 404 })); + } + return new Promise((resolve, reject) => { itemApiInstance.share( { id, permissions },