diff --git a/packages/nodes-base/nodes/Microsoft/Outlook/test/v2/methods/listSearch.test.ts b/packages/nodes-base/nodes/Microsoft/Outlook/test/v2/methods/listSearch.test.ts index dd724e006496c..92e4774eff52a 100644 --- a/packages/nodes-base/nodes/Microsoft/Outlook/test/v2/methods/listSearch.test.ts +++ b/packages/nodes-base/nodes/Microsoft/Outlook/test/v2/methods/listSearch.test.ts @@ -405,7 +405,7 @@ describe('MicrosoftOutlookV2 - listSearch methods', () => { $top: 100, }, ); - expect(mockTransport.getSubfolders).toHaveBeenCalledWith(mockResponse.value); + expect(mockTransport.getSubfolders).toHaveBeenCalledWith(mockResponse.value, true); expect(result).toEqual({ results: [ { diff --git a/packages/nodes-base/nodes/Microsoft/Outlook/test/v2/methods/loadOptions.test.ts b/packages/nodes-base/nodes/Microsoft/Outlook/test/v2/methods/loadOptions.test.ts index 48656418ddb82..e0544aeba1c53 100644 --- a/packages/nodes-base/nodes/Microsoft/Outlook/test/v2/methods/loadOptions.test.ts +++ b/packages/nodes-base/nodes/Microsoft/Outlook/test/v2/methods/loadOptions.test.ts @@ -126,7 +126,7 @@ describe('MicrosoftOutlookV2 - loadOptions methods', () => { '/mailFolders', {}, ); - expect(mockTransport.getSubfolders).toHaveBeenCalledWith(mockResponse); + expect(mockTransport.getSubfolders).toHaveBeenCalledWith(mockResponse, true); expect(result).toEqual([ { name: 'Inbox', value: 'folder1' }, { name: 'Sent Items', value: 'folder2' }, diff --git a/packages/nodes-base/nodes/Microsoft/Outlook/test/v2/transport/index.test.ts b/packages/nodes-base/nodes/Microsoft/Outlook/test/v2/transport/index.test.ts new file mode 100644 index 0000000000000..b9382aba2ec62 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/Outlook/test/v2/transport/index.test.ts @@ -0,0 +1,94 @@ +import { mockDeep } from 'jest-mock-extended'; +import type { ILoadOptionsFunctions } from 'n8n-workflow'; + +import { getSubfolders } from '../../../v2/transport'; + +describe('MicrosoftOutlookV2 - getSubfolders', () => { + let mockLoadOptionsFunctions: jest.Mocked; + + beforeEach(() => { + mockLoadOptionsFunctions = mockDeep(); + mockLoadOptionsFunctions.getCredentials.mockResolvedValue({}); + jest.clearAllMocks(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should not request childFolders when childFolderCount is 0', async () => { + const folders = [ + { id: 'folder1', displayName: 'Inbox', childFolderCount: 0 }, + { id: 'folder2', displayName: 'Sent Items', childFolderCount: 0 }, + ]; + + const result = await getSubfolders.call(mockLoadOptionsFunctions, folders); + + expect( + mockLoadOptionsFunctions.helpers.requestWithAuthentication as jest.Mock, + ).not.toHaveBeenCalled(); + expect(result).toEqual(folders); + }); + + it('should paginate child folder requests using nextLink', async () => { + const folders = [{ id: 'inbox', displayName: 'Inbox', childFolderCount: 2 }]; + + (mockLoadOptionsFunctions.helpers.requestWithAuthentication as jest.Mock) + .mockResolvedValueOnce({ + value: [{ id: 'sub1', displayName: 'Work', childFolderCount: 0 }], + '@odata.nextLink': + 'https://graph.microsoft.com/v1.0/me/mailFolders/inbox/childFolders?$skip=1', + }) + .mockResolvedValueOnce({ + value: [{ id: 'sub2', displayName: 'Projects', childFolderCount: 0 }], + }); + + const result = await getSubfolders.call(mockLoadOptionsFunctions, folders); + + expect( + mockLoadOptionsFunctions.helpers.requestWithAuthentication as jest.Mock, + ).toHaveBeenCalledTimes(2); + expect(result).toEqual([ + { id: 'inbox', displayName: 'Inbox', childFolderCount: 2 }, + { id: 'sub1', displayName: 'Work', childFolderCount: 0 }, + { id: 'sub2', displayName: 'Projects', childFolderCount: 0 }, + ]); + }); + + it('should prefix nested subfolder displayNames with full parent path', async () => { + const folders = [{ id: 'inbox', displayName: 'Inbox', childFolderCount: 1 }]; + + (mockLoadOptionsFunctions.helpers.requestWithAuthentication as jest.Mock) + .mockResolvedValueOnce({ + value: [{ id: 'work', displayName: 'Work', childFolderCount: 1 }], + }) + .mockResolvedValueOnce({ + value: [{ id: 'q2', displayName: 'Q2', childFolderCount: 0 }], + }); + + const result = await getSubfolders.call(mockLoadOptionsFunctions, folders, true); + + expect(result).toEqual([ + { id: 'inbox', displayName: 'Inbox', childFolderCount: 1 }, + { id: 'work', displayName: 'Inbox/Work', childFolderCount: 1 }, + { id: 'q2', displayName: 'Inbox/Work/Q2', childFolderCount: 0 }, + ]); + }); + + it('should return bare subfolder displayNames when addPathToDisplayName is false', async () => { + const folders = [{ id: 'inbox', displayName: 'Inbox', childFolderCount: 1 }]; + + (mockLoadOptionsFunctions.helpers.requestWithAuthentication as jest.Mock).mockResolvedValueOnce( + { + value: [{ id: 'work', displayName: 'Work', childFolderCount: 0 }], + }, + ); + + const result = await getSubfolders.call(mockLoadOptionsFunctions, folders); + + expect(result).toEqual([ + { id: 'inbox', displayName: 'Inbox', childFolderCount: 1 }, + { id: 'work', displayName: 'Work', childFolderCount: 0 }, + ]); + }); +}); diff --git a/packages/nodes-base/nodes/Microsoft/Outlook/v2/methods/listSearch.ts b/packages/nodes-base/nodes/Microsoft/Outlook/v2/methods/listSearch.ts index ecdd99bf370a3..c50c91d7f85a8 100644 --- a/packages/nodes-base/nodes/Microsoft/Outlook/v2/methods/listSearch.ts +++ b/packages/nodes-base/nodes/Microsoft/Outlook/v2/methods/listSearch.ts @@ -223,7 +223,7 @@ export async function searchFolders( response = await microsoftApiRequest.call(this, 'GET', '/mailFolders', undefined, qs); } - let folders = await getSubfolders.call(this, response.value as IDataObject[]); + let folders = await getSubfolders.call(this, response.value as IDataObject[], true); if (filter) { filter = filter.toLowerCase(); diff --git a/packages/nodes-base/nodes/Microsoft/Outlook/v2/methods/loadOptions.ts b/packages/nodes-base/nodes/Microsoft/Outlook/v2/methods/loadOptions.ts index 7e32e8ba0a24b..9896ada367518 100644 --- a/packages/nodes-base/nodes/Microsoft/Outlook/v2/methods/loadOptions.ts +++ b/packages/nodes-base/nodes/Microsoft/Outlook/v2/methods/loadOptions.ts @@ -24,7 +24,7 @@ export async function getCategoriesNames( export async function getFolders(this: ILoadOptionsFunctions): Promise { const returnData: INodePropertyOptions[] = []; const response = await microsoftApiRequestAllItems.call(this, 'value', 'GET', '/mailFolders', {}); - const folders = await getSubfolders.call(this, response); + const folders = await getSubfolders.call(this, response, true); for (const folder of folders) { returnData.push({ name: folder.displayName as string, diff --git a/packages/nodes-base/nodes/Microsoft/Outlook/v2/transport/index.ts b/packages/nodes-base/nodes/Microsoft/Outlook/v2/transport/index.ts index 4ea65527dbeb1..8ada9701332d7 100644 --- a/packages/nodes-base/nodes/Microsoft/Outlook/v2/transport/index.ts +++ b/packages/nodes-base/nodes/Microsoft/Outlook/v2/transport/index.ts @@ -206,21 +206,20 @@ export async function getSubfolders( const returnData: IDataObject[] = [...folders]; for (const folder of folders) { if ((folder.childFolderCount as number) > 0) { - let subfolders = await microsoftApiRequest.call( + let subfolders = await microsoftApiRequestAllItems.call( this, + 'value', 'GET', `/mailFolders/${folder.id}/childFolders`, ); if (addPathToDisplayName) { - subfolders = subfolders.value.map((subfolder: IDataObject) => { + subfolders = subfolders.map((subfolder: IDataObject) => { return { ...subfolder, displayName: `${folder.displayName}/${subfolder.displayName}`, }; }); - } else { - subfolders = subfolders.value; } returnData.push(