mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-12 02:19:43 +00:00
fix(Microsoft Outlook Node): Resolve service principal mailbox per input item (#34110)
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"issues": 558
|
||||
"issues": 561
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ describe('Microsoft Outlook Trigger GenericFunctions', () => {
|
||||
it('should handle simple output format in manual mode', async () => {
|
||||
const result = await getPollResponse.call(mockPollFunctions, pollStartDate, pollEndDate);
|
||||
|
||||
expect(microsoftApiRequest).toHaveBeenCalledWith('GET', '/messages', undefined, {
|
||||
expect(microsoftApiRequest).toHaveBeenCalledWith('GET', '/messages', 0, undefined, {
|
||||
$select:
|
||||
'id,conversationId,subject,bodyPreview,from,toRecipients,categories,hasAttachments',
|
||||
$top: 1,
|
||||
@@ -122,7 +122,7 @@ describe('Microsoft Outlook Trigger GenericFunctions', () => {
|
||||
|
||||
const result = await getPollResponse.call(mockPollFunctions, pollStartDate, pollEndDate);
|
||||
|
||||
expect(microsoftApiRequest).toHaveBeenCalledWith('GET', '/messages', undefined, {
|
||||
expect(microsoftApiRequest).toHaveBeenCalledWith('GET', '/messages', 0, undefined, {
|
||||
$select: 'id,subject,from',
|
||||
$top: 1,
|
||||
});
|
||||
@@ -152,11 +152,11 @@ describe('Microsoft Outlook Trigger GenericFunctions', () => {
|
||||
|
||||
const result = await getPollResponse.call(mockPollFunctions, pollStartDate, pollEndDate);
|
||||
|
||||
expect(microsoftApiRequest).toHaveBeenCalledWith('GET', '/messages', undefined, {
|
||||
expect(microsoftApiRequest).toHaveBeenCalledWith('GET', '/messages', 0, undefined, {
|
||||
$select: 'id,subject,from,hasAttachments',
|
||||
$top: 1,
|
||||
});
|
||||
expect(downloadAttachments).toHaveBeenCalledWith([mockMessages[0]], 'attachment_');
|
||||
expect(downloadAttachments).toHaveBeenCalledWith([mockMessages[0]], 'attachment_', 0);
|
||||
expect(result).toEqual(mockExecutionData);
|
||||
});
|
||||
});
|
||||
@@ -174,6 +174,7 @@ describe('Microsoft Outlook Trigger GenericFunctions', () => {
|
||||
'value',
|
||||
'GET',
|
||||
'/messages',
|
||||
0,
|
||||
undefined,
|
||||
{
|
||||
$select:
|
||||
@@ -204,6 +205,7 @@ describe('Microsoft Outlook Trigger GenericFunctions', () => {
|
||||
'value',
|
||||
'GET',
|
||||
'/messages',
|
||||
0,
|
||||
undefined,
|
||||
{
|
||||
$select: 'id,subject,receivedDateTime',
|
||||
@@ -224,6 +226,7 @@ describe('Microsoft Outlook Trigger GenericFunctions', () => {
|
||||
'value',
|
||||
'GET',
|
||||
'/messages',
|
||||
0,
|
||||
undefined,
|
||||
{
|
||||
$select:
|
||||
@@ -255,7 +258,7 @@ describe('Microsoft Outlook Trigger GenericFunctions', () => {
|
||||
|
||||
const result = await getPollResponse.call(mockPollFunctions, pollStartDate, pollEndDate);
|
||||
|
||||
expect(downloadAttachments).toHaveBeenCalledWith(mockMessages, 'custom_');
|
||||
expect(downloadAttachments).toHaveBeenCalledWith(mockMessages, 'custom_', 0);
|
||||
expect(result).toEqual(mockExecutionData);
|
||||
});
|
||||
|
||||
@@ -281,7 +284,7 @@ describe('Microsoft Outlook Trigger GenericFunctions', () => {
|
||||
|
||||
await getPollResponse.call(mockPollFunctions, pollStartDate, pollEndDate);
|
||||
|
||||
expect(downloadAttachments).toHaveBeenCalledWith(mockMessages, 'attachment_');
|
||||
expect(downloadAttachments).toHaveBeenCalledWith(mockMessages, 'attachment_', 0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -313,6 +316,7 @@ describe('Microsoft Outlook Trigger GenericFunctions', () => {
|
||||
'value',
|
||||
'GET',
|
||||
`/mailFolders/${folderId1}/messages`,
|
||||
0,
|
||||
undefined,
|
||||
{
|
||||
$select:
|
||||
@@ -352,6 +356,7 @@ describe('Microsoft Outlook Trigger GenericFunctions', () => {
|
||||
'value',
|
||||
'GET',
|
||||
`/mailFolders/${folderId1}/messages`,
|
||||
0,
|
||||
undefined,
|
||||
expect.objectContaining({ $filter: expect.any(String) }),
|
||||
);
|
||||
@@ -359,6 +364,7 @@ describe('Microsoft Outlook Trigger GenericFunctions', () => {
|
||||
'value',
|
||||
'GET',
|
||||
`/mailFolders/${folderId2}/messages`,
|
||||
0,
|
||||
undefined,
|
||||
expect.objectContaining({ $filter: expect.any(String) }),
|
||||
);
|
||||
@@ -402,6 +408,7 @@ describe('Microsoft Outlook Trigger GenericFunctions', () => {
|
||||
'value',
|
||||
'GET',
|
||||
'/messages',
|
||||
0,
|
||||
undefined,
|
||||
expect.objectContaining({ $filter: expect.any(String) }),
|
||||
);
|
||||
@@ -432,6 +439,7 @@ describe('Microsoft Outlook Trigger GenericFunctions', () => {
|
||||
expect(microsoftApiRequest).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
`/mailFolders/${folderId1}/messages`,
|
||||
0,
|
||||
undefined,
|
||||
{
|
||||
$select:
|
||||
@@ -442,6 +450,7 @@ describe('Microsoft Outlook Trigger GenericFunctions', () => {
|
||||
expect(microsoftApiRequest).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
`/mailFolders/${folderId2}/messages`,
|
||||
0,
|
||||
undefined,
|
||||
{
|
||||
$select:
|
||||
@@ -500,7 +509,7 @@ describe('Microsoft Outlook Trigger GenericFunctions', () => {
|
||||
|
||||
const result = await getPollResponse.call(mockPollFunctions, pollStartDate, pollEndDate);
|
||||
|
||||
expect(microsoftApiRequest).toHaveBeenCalledWith('GET', '/messages', undefined, {
|
||||
expect(microsoftApiRequest).toHaveBeenCalledWith('GET', '/messages', 0, undefined, {
|
||||
$top: 1,
|
||||
});
|
||||
expect(simplifyOutputMessages).not.toHaveBeenCalled();
|
||||
@@ -550,7 +559,7 @@ describe('Microsoft Outlook Trigger GenericFunctions', () => {
|
||||
|
||||
await getPollResponse.call(mockPollFunctions, pollStartDate, pollEndDate);
|
||||
|
||||
expect(microsoftApiRequest).toHaveBeenCalledWith('GET', '/messages', undefined, {
|
||||
expect(microsoftApiRequest).toHaveBeenCalledWith('GET', '/messages', 0, undefined, {
|
||||
$select: 'id,subject,hasAttachments',
|
||||
$top: 1,
|
||||
});
|
||||
@@ -719,7 +728,7 @@ describe('Microsoft Outlook Trigger GenericFunctions', () => {
|
||||
|
||||
const result = await getPollResponse.call(mockPollFunctions, pollStartDate, pollEndDate);
|
||||
|
||||
expect(microsoftApiRequest).toHaveBeenCalledWith('GET', '/messages', undefined, {
|
||||
expect(microsoftApiRequest).toHaveBeenCalledWith('GET', '/messages', 0, undefined, {
|
||||
$select: '',
|
||||
$top: 1,
|
||||
});
|
||||
@@ -754,6 +763,7 @@ describe('Microsoft Outlook Trigger GenericFunctions', () => {
|
||||
'value',
|
||||
'GET',
|
||||
'/messages',
|
||||
0,
|
||||
undefined,
|
||||
{
|
||||
$select: 'id,subject,from,isRead',
|
||||
@@ -792,13 +802,14 @@ describe('Microsoft Outlook Trigger GenericFunctions', () => {
|
||||
'value',
|
||||
'GET',
|
||||
'/messages',
|
||||
0,
|
||||
undefined,
|
||||
{
|
||||
$select: 'id,subject,hasAttachments',
|
||||
$filter: `isRead eq false and receivedDateTime ge ${pollStartDate} and receivedDateTime lt ${pollEndDate}`,
|
||||
},
|
||||
);
|
||||
expect(downloadAttachments).toHaveBeenCalledWith(mockMessages, 'prefix_');
|
||||
expect(downloadAttachments).toHaveBeenCalledWith(mockMessages, 'prefix_', 0);
|
||||
expect(result).toEqual(mockExecutionData);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import type { IExecuteFunctions, INode } from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
import type { Mock, Mocked } from 'vitest';
|
||||
import { mockDeep } from 'vitest-mock-extended';
|
||||
|
||||
import { router } from '../../../v2/actions/router';
|
||||
|
||||
describe('MicrosoftOutlookV2 - router error attribution', () => {
|
||||
it('stamps context.itemIndex on a NodeError from the failing item', async () => {
|
||||
const mockExecuteFunctions: Mocked<IExecuteFunctions> = mockDeep<IExecuteFunctions>();
|
||||
const mockNode: INode = {
|
||||
id: 'test-node',
|
||||
name: 'Test Outlook Node',
|
||||
type: 'n8n-nodes-base.microsoftOutlook',
|
||||
typeVersion: 2,
|
||||
position: [0, 0],
|
||||
parameters: {},
|
||||
};
|
||||
mockExecuteFunctions.getNode.mockReturnValue(mockNode);
|
||||
mockExecuteFunctions.getInputData.mockReturnValue([{ json: {} }, { json: {} }]);
|
||||
mockExecuteFunctions.continueOnFail.mockReturnValue(false);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
accessToken: 'test-access-token',
|
||||
graphApiBaseUrl: '',
|
||||
});
|
||||
mockExecuteFunctions.helpers.requestWithAuthentication = vi.fn().mockResolvedValue({});
|
||||
(mockExecuteFunctions.helpers.constructExecutionMetaData as Mock).mockReturnValue([]);
|
||||
// message:delete on two items: item 0's mailbox is valid, item 1's is not — the
|
||||
// unstamped validator error must leave the router carrying item 1's index.
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((name, itemIndex, fallback) => {
|
||||
const params: Record<string, unknown> = {
|
||||
resource: 'message',
|
||||
operation: 'delete',
|
||||
authentication: 'microsoftEntraServicePrincipalApi',
|
||||
messageId: 'MSG1',
|
||||
};
|
||||
if (name === 'mailbox')
|
||||
return (itemIndex === 1 ? 'contoso.com' : '[email protected]') as never;
|
||||
return (name in params ? params[name] : fallback) as never;
|
||||
});
|
||||
|
||||
let caught: unknown;
|
||||
try {
|
||||
await router.call(mockExecuteFunctions);
|
||||
} catch (error) {
|
||||
caught = error;
|
||||
}
|
||||
|
||||
expect(caught).toBeInstanceOf(NodeOperationError);
|
||||
expect((caught as NodeOperationError).message).toBe('The mailbox is not valid');
|
||||
expect((caught as NodeOperationError).context.itemIndex).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -49,6 +49,7 @@ describe('MicrosoftOutlookV2 - listSearch methods', () => {
|
||||
expect(mockTransport.microsoftApiRequest).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
'/contacts',
|
||||
0,
|
||||
undefined,
|
||||
{
|
||||
$select: 'id,displayName',
|
||||
@@ -76,6 +77,7 @@ describe('MicrosoftOutlookV2 - listSearch methods', () => {
|
||||
expect(mockTransport.microsoftApiRequest).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
'/contacts',
|
||||
0,
|
||||
undefined,
|
||||
{
|
||||
$select: 'id,displayName',
|
||||
@@ -102,6 +104,7 @@ describe('MicrosoftOutlookV2 - listSearch methods', () => {
|
||||
expect(mockTransport.microsoftApiRequest).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
'',
|
||||
0,
|
||||
undefined,
|
||||
undefined,
|
||||
paginationToken,
|
||||
@@ -125,6 +128,7 @@ describe('MicrosoftOutlookV2 - listSearch methods', () => {
|
||||
expect(mockTransport.microsoftApiRequest).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
'/calendars',
|
||||
0,
|
||||
undefined,
|
||||
{
|
||||
$select: 'id,name',
|
||||
@@ -161,6 +165,7 @@ describe('MicrosoftOutlookV2 - listSearch methods', () => {
|
||||
expect(mockTransport.microsoftApiRequest).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
'/messages',
|
||||
0,
|
||||
undefined,
|
||||
{
|
||||
$select: 'id,subject,bodyPreview,webLink',
|
||||
@@ -199,6 +204,7 @@ describe('MicrosoftOutlookV2 - listSearch methods', () => {
|
||||
expect(mockTransport.microsoftApiRequest).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
'/messages',
|
||||
0,
|
||||
undefined,
|
||||
{
|
||||
$select: 'id,subject,bodyPreview,webLink',
|
||||
@@ -248,6 +254,7 @@ describe('MicrosoftOutlookV2 - listSearch methods', () => {
|
||||
expect(mockTransport.microsoftApiRequest).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
'/messages',
|
||||
0,
|
||||
undefined,
|
||||
{
|
||||
$select: 'id,subject,bodyPreview,webLink',
|
||||
@@ -285,6 +292,7 @@ describe('MicrosoftOutlookV2 - listSearch methods', () => {
|
||||
expect(mockTransport.microsoftApiRequest).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
'/messages',
|
||||
0,
|
||||
undefined,
|
||||
{
|
||||
$select: 'id,subject,bodyPreview,webLink',
|
||||
@@ -327,6 +335,7 @@ describe('MicrosoftOutlookV2 - listSearch methods', () => {
|
||||
expect(mockTransport.microsoftApiRequest).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
`/calendars/${calendarId}/events`,
|
||||
0,
|
||||
undefined,
|
||||
{
|
||||
$select: 'id,subject,bodyPreview',
|
||||
@@ -365,6 +374,7 @@ describe('MicrosoftOutlookV2 - listSearch methods', () => {
|
||||
expect(mockTransport.microsoftApiRequest).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
`/calendars/${calendarId}/events`,
|
||||
0,
|
||||
undefined,
|
||||
{
|
||||
$select: 'id,subject,bodyPreview',
|
||||
@@ -401,12 +411,13 @@ describe('MicrosoftOutlookV2 - listSearch methods', () => {
|
||||
expect(mockTransport.microsoftApiRequest).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
'/mailFolders',
|
||||
0,
|
||||
undefined,
|
||||
{
|
||||
$top: 100,
|
||||
},
|
||||
);
|
||||
expect(mockTransport.getSubfolders).toHaveBeenCalledWith(mockResponse.value, true);
|
||||
expect(mockTransport.getSubfolders).toHaveBeenCalledWith(mockResponse.value, 0, true);
|
||||
expect(result).toEqual({
|
||||
results: [
|
||||
{
|
||||
@@ -506,6 +517,7 @@ describe('MicrosoftOutlookV2 - listSearch methods', () => {
|
||||
expect(mockTransport.microsoftApiRequest).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
`/messages/${messageId}/attachments`,
|
||||
0,
|
||||
undefined,
|
||||
{
|
||||
$select: 'id,name',
|
||||
@@ -537,6 +549,7 @@ describe('MicrosoftOutlookV2 - listSearch methods', () => {
|
||||
expect(mockTransport.microsoftApiRequest).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
'',
|
||||
0,
|
||||
undefined,
|
||||
undefined,
|
||||
paginationToken,
|
||||
|
||||
@@ -37,6 +37,7 @@ describe('MicrosoftOutlookV2 - loadOptions methods', () => {
|
||||
'value',
|
||||
'GET',
|
||||
'/outlook/masterCategories',
|
||||
0,
|
||||
);
|
||||
expect(result).toEqual([
|
||||
{ name: 'Red Category', value: 'Red Category' },
|
||||
@@ -125,9 +126,10 @@ describe('MicrosoftOutlookV2 - loadOptions methods', () => {
|
||||
'value',
|
||||
'GET',
|
||||
'/mailFolders',
|
||||
0,
|
||||
{},
|
||||
);
|
||||
expect(mockTransport.getSubfolders).toHaveBeenCalledWith(mockResponse, true);
|
||||
expect(mockTransport.getSubfolders).toHaveBeenCalledWith(mockResponse, 0, true);
|
||||
expect(result).toEqual([
|
||||
{ name: 'Inbox', value: 'folder1' },
|
||||
{ name: 'Sent Items', value: 'folder2' },
|
||||
@@ -222,6 +224,7 @@ describe('MicrosoftOutlookV2 - loadOptions methods', () => {
|
||||
'value',
|
||||
'GET',
|
||||
'/calendarGroups',
|
||||
0,
|
||||
{},
|
||||
);
|
||||
expect(result).toEqual([
|
||||
|
||||
@@ -190,6 +190,7 @@ describe('Test MicrosoftOutlookV2, draft => create', () => {
|
||||
expect(microsoftApiRequestSpy).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/messages',
|
||||
0,
|
||||
expect.objectContaining({
|
||||
subject: 'Test Draft with Attachment',
|
||||
body: expect.objectContaining({
|
||||
@@ -243,11 +244,14 @@ describe('Test MicrosoftOutlookV2, draft => create', () => {
|
||||
|
||||
microsoftApiRequestSpy.mockResolvedValue({});
|
||||
|
||||
await execute.call(mockExecuteFunctions, 0, [{ json: {} }]);
|
||||
// Non-zero index: pins that the operation forwards the PASSED index (not a
|
||||
// hardcoded 0). The getNodeParameter mock is index-insensitive.
|
||||
await execute.call(mockExecuteFunctions, 2, [{ json: {} }]);
|
||||
|
||||
expect(microsoftApiRequestSpy).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/messages',
|
||||
2,
|
||||
expect.objectContaining({
|
||||
attachments: [
|
||||
{
|
||||
@@ -296,6 +300,7 @@ describe('Test MicrosoftOutlookV2, draft => create', () => {
|
||||
expect(microsoftApiRequestSpy).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/messages',
|
||||
0,
|
||||
expect.objectContaining({
|
||||
attachments: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@ describe('Test MicrosoftOutlookV2, message => sendAndWait', () => {
|
||||
expect(transport.microsoftApiRequest).toHaveBeenCalledTimes(1);
|
||||
expect(mockExecuteFunctions.putExecutionToWait).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(transport.microsoftApiRequest).toHaveBeenCalledWith('POST', '/sendMail', {
|
||||
expect(transport.microsoftApiRequest).toHaveBeenCalledWith('POST', '/sendMail', 0, {
|
||||
message: {
|
||||
body: {
|
||||
content: expect.stringContaining(
|
||||
|
||||
+6
-1
@@ -90,6 +90,7 @@ describe('Microsoft Outlook V2 - MessageAttachment:add', () => {
|
||||
expect(microsoftApiRequestSpy).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/messages/AAMkAGI2TG93AAA=/attachments',
|
||||
0,
|
||||
{
|
||||
'@odata.type': '#microsoft.graph.fileAttachment',
|
||||
name: 'test-file.txt',
|
||||
@@ -132,11 +133,14 @@ describe('Microsoft Outlook V2 - MessageAttachment:add', () => {
|
||||
|
||||
microsoftApiRequestSpy.mockResolvedValue({});
|
||||
|
||||
await execute.call(mockExecuteFunctions, 0, [{ json: {} }]);
|
||||
// Non-zero index: pins that the operation forwards the PASSED index (not a
|
||||
// hardcoded 0). The getNodeParameter mock is index-insensitive.
|
||||
await execute.call(mockExecuteFunctions, 2, [{ json: {} }]);
|
||||
|
||||
expect(microsoftApiRequestSpy).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/messages/AAMkAGI2TG93AAA=/attachments',
|
||||
2,
|
||||
{
|
||||
'@odata.type': '#microsoft.graph.fileAttachment',
|
||||
name: 'custom-name.pdf',
|
||||
@@ -222,6 +226,7 @@ describe('Microsoft Outlook V2 - MessageAttachment:add', () => {
|
||||
expect(microsoftApiRequestSpy).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/messages/AAMkAGI2TG93BBB=/attachments/createUploadSession',
|
||||
0,
|
||||
{
|
||||
AttachmentItem: {
|
||||
attachmentType: 'file',
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
// folder:getAll with includeChildFolders exercises the helper chain end-to-end:
|
||||
// operation -> microsoftApiRequest AND getSubfolders -> microsoftApiRequestAllItems ->
|
||||
// (recursive) getSubfolders. Fanning out to two mailboxes proves the loop index is
|
||||
// threaded through every hop — the child-folder fetch for item i's parent list must hit
|
||||
// item i's mailbox, not item 0's (which would cross-mix mailboxes). Consume-once
|
||||
// interceptors + empty pendingMocks pin the exact request set.
|
||||
describe('Test MicrosoftOutlookV2, Service Principal => folder:getAll per-item mailbox', () => {
|
||||
const credentials = {
|
||||
microsoftEntraServicePrincipalApi: {
|
||||
accessToken: 'test-access-token',
|
||||
tenantId: '11111111-1111-1111-1111-111111111111',
|
||||
clientId: '22222222-2222-2222-2222-222222222222',
|
||||
clientSecret: 'secret',
|
||||
graphApiBaseUrl: 'https://graph.microsoft.com',
|
||||
},
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
nock('https://graph.microsoft.com')
|
||||
.matchHeader('Authorization', 'Bearer test-access-token')
|
||||
.get('/v1.0/users/usera%40example.com/mailFolders')
|
||||
.query({ $top: 50 })
|
||||
.reply(200, {
|
||||
value: [{ id: 'folder-a', displayName: 'Inbox A', childFolderCount: 1 }],
|
||||
});
|
||||
|
||||
// childFolderCount > 0 makes getSubfolders recurse into this folder — the child
|
||||
// fetch must stay on mailbox A.
|
||||
nock('https://graph.microsoft.com')
|
||||
.matchHeader('Authorization', 'Bearer test-access-token')
|
||||
.get('/v1.0/users/usera%40example.com/mailFolders/folder-a/childFolders')
|
||||
.query({ $top: 100 })
|
||||
.reply(200, {
|
||||
value: [{ id: 'sub-a', displayName: 'Sub A', childFolderCount: 0 }],
|
||||
});
|
||||
|
||||
nock('https://graph.microsoft.com')
|
||||
.matchHeader('Authorization', 'Bearer test-access-token')
|
||||
.get('/v1.0/users/userb%40example.com/mailFolders')
|
||||
.query({ $top: 50 })
|
||||
.reply(200, {
|
||||
value: [{ id: 'folder-b', displayName: 'Inbox B', childFolderCount: 1 }],
|
||||
});
|
||||
|
||||
nock('https://graph.microsoft.com')
|
||||
.matchHeader('Authorization', 'Bearer test-access-token')
|
||||
.get('/v1.0/users/userb%40example.com/mailFolders/folder-b/childFolders')
|
||||
.query({ $top: 100 })
|
||||
.reply(200, {
|
||||
value: [{ id: 'sub-b', displayName: 'Sub B', childFolderCount: 0 }],
|
||||
});
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['folderGetAllPerItem.workflow.json'],
|
||||
customAssertions: () => expect(nock.pendingMocks()).toEqual([]),
|
||||
});
|
||||
});
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
{
|
||||
"name": "Microsoft Outlook Service Principal Folder Get All Per Item",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "e524f588-b6a3-4849-8777-b32a8a755af0",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [820, 360]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"data": [{ "mailbox": "[email protected]" }, { "mailbox": "[email protected]" }]
|
||||
},
|
||||
"id": "e524f588-b6a3-4849-8777-b32a8a755af1",
|
||||
"name": "Test Data",
|
||||
"type": "n8n-nodes-testing.testData",
|
||||
"typeVersion": 1,
|
||||
"position": [930, 360]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "microsoftEntraServicePrincipalApi",
|
||||
"mailbox": {
|
||||
"__rl": true,
|
||||
"mode": "id",
|
||||
"value": "={{ $json.mailbox }}"
|
||||
},
|
||||
"resource": "folder",
|
||||
"operation": "getAll",
|
||||
"returnAll": false,
|
||||
"limit": 50,
|
||||
"filters": {},
|
||||
"options": {
|
||||
"includeChildFolders": true
|
||||
}
|
||||
},
|
||||
"id": "baff6798-0304-4255-bdb0-dd3f2659373d",
|
||||
"name": "Microsoft Outlook",
|
||||
"type": "n8n-nodes-base.microsoftOutlook",
|
||||
"typeVersion": 2,
|
||||
"position": [1040, 360],
|
||||
"credentials": {
|
||||
"microsoftEntraServicePrincipalApi": {
|
||||
"id": "spCredId1",
|
||||
"name": "Microsoft Entra Service Principal Test"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Microsoft Outlook": [
|
||||
{
|
||||
"json": {
|
||||
"id": "folder-a",
|
||||
"displayName": "Inbox A",
|
||||
"childFolderCount": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "sub-a",
|
||||
"displayName": "Sub A",
|
||||
"childFolderCount": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "folder-b",
|
||||
"displayName": "Inbox B",
|
||||
"childFolderCount": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "sub-b",
|
||||
"displayName": "Sub B",
|
||||
"childFolderCount": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Test Data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Test Data": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Microsoft Outlook",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
// The mailbox RLC accepts expressions, so it must be resolved per input item: a fan-out
|
||||
// to two mailboxes must produce one /users/{encoded-mailbox}/sendMail POST per mailbox.
|
||||
// Both interceptors are consume-once and `pendingMocks` must be empty afterwards, so a
|
||||
// run that sends both items from item 0's mailbox fails in either direction.
|
||||
describe('Test MicrosoftOutlookV2, Service Principal => message:send per-item mailbox', () => {
|
||||
const credentials = {
|
||||
microsoftEntraServicePrincipalApi: {
|
||||
accessToken: 'test-access-token',
|
||||
tenantId: '11111111-1111-1111-1111-111111111111',
|
||||
clientId: '22222222-2222-2222-2222-222222222222',
|
||||
clientSecret: 'secret',
|
||||
graphApiBaseUrl: 'https://graph.microsoft.com',
|
||||
},
|
||||
};
|
||||
|
||||
const expectedBody = {
|
||||
message: {
|
||||
body: { content: 'message description', contentType: 'Text' },
|
||||
subject: 'Hello',
|
||||
toRecipients: [{ emailAddress: { address: '[email protected]' } }],
|
||||
},
|
||||
saveToSentItems: true,
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
nock('https://graph.microsoft.com')
|
||||
.matchHeader('Authorization', 'Bearer test-access-token')
|
||||
.post('/v1.0/users/alice%40example.com/sendMail', expectedBody)
|
||||
.reply(200, {});
|
||||
|
||||
nock('https://graph.microsoft.com')
|
||||
.matchHeader('Authorization', 'Bearer test-access-token')
|
||||
.post('/v1.0/users/bob%40example.com/sendMail', expectedBody)
|
||||
.reply(200, {});
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['sendPerItem.workflow.json'],
|
||||
customAssertions: () => expect(nock.pendingMocks()).toEqual([]),
|
||||
});
|
||||
});
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
{
|
||||
"name": "Microsoft Outlook Service Principal Message Send Per Item",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "e524f588-b6a3-4849-8777-b32a8a755ae6",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [820, 360]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"data": [{ "mailbox": "[email protected]" }, { "mailbox": "[email protected]" }]
|
||||
},
|
||||
"id": "e524f588-b6a3-4849-8777-b32a8a755ae7",
|
||||
"name": "Test Data",
|
||||
"type": "n8n-nodes-testing.testData",
|
||||
"typeVersion": 1,
|
||||
"position": [930, 360]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "microsoftEntraServicePrincipalApi",
|
||||
"mailbox": {
|
||||
"__rl": true,
|
||||
"mode": "id",
|
||||
"value": "={{ $json.mailbox }}"
|
||||
},
|
||||
"resource": "message",
|
||||
"operation": "send",
|
||||
"toRecipients": "[email protected]",
|
||||
"subject": "Hello",
|
||||
"bodyContent": "message description",
|
||||
"additionalFields": {
|
||||
"bodyContentType": "Text"
|
||||
}
|
||||
},
|
||||
"id": "baff6798-0304-4255-bdb0-dd3f2659373c",
|
||||
"name": "Microsoft Outlook",
|
||||
"type": "n8n-nodes-base.microsoftOutlook",
|
||||
"typeVersion": 2,
|
||||
"position": [1040, 360],
|
||||
"credentials": {
|
||||
"microsoftEntraServicePrincipalApi": {
|
||||
"id": "spCredId1",
|
||||
"name": "Microsoft Entra Service Principal Test"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Microsoft Outlook": [
|
||||
{
|
||||
"json": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Test Data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Test Data": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Microsoft Outlook",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
// Per-item error isolation under continueOnFail: item 0's bogus mailbox ("contoso.com"
|
||||
// is a bare host, not a GUID/UPN) must route through the router's continueOnFail branch
|
||||
// as an error item for item 0 only, while items 1 and 2 still send from their own
|
||||
// mailboxes. Only the two valid mailboxes are mocked; consume-once + empty pendingMocks
|
||||
// pin the exact request set.
|
||||
describe('Test MicrosoftOutlookV2, Service Principal => message:send fails only the bad item under continueOnFail', () => {
|
||||
const credentials = {
|
||||
microsoftEntraServicePrincipalApi: {
|
||||
accessToken: 'test-access-token',
|
||||
tenantId: '11111111-1111-1111-1111-111111111111',
|
||||
clientId: '22222222-2222-2222-2222-222222222222',
|
||||
clientSecret: 'secret',
|
||||
graphApiBaseUrl: 'https://graph.microsoft.com',
|
||||
},
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
nock('https://graph.microsoft.com')
|
||||
.matchHeader('Authorization', 'Bearer test-access-token')
|
||||
.post('/v1.0/users/alice%40example.com/sendMail')
|
||||
.reply(200, {});
|
||||
|
||||
nock('https://graph.microsoft.com')
|
||||
.matchHeader('Authorization', 'Bearer test-access-token')
|
||||
.post('/v1.0/users/bob%40example.com/sendMail')
|
||||
.reply(200, {});
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['sendPerItemContinueOnFail.workflow.json'],
|
||||
customAssertions: () => expect(nock.pendingMocks()).toEqual([]),
|
||||
});
|
||||
});
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
{
|
||||
"name": "Microsoft Outlook Service Principal Message Send Per Item ContinueOnFail",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "e524f588-b6a3-4849-8777-b32a8a755af5",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [820, 360]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"data": [
|
||||
{ "mailbox": "contoso.com" },
|
||||
{ "mailbox": "[email protected]" },
|
||||
{ "mailbox": "[email protected]" }
|
||||
]
|
||||
},
|
||||
"id": "e524f588-b6a3-4849-8777-b32a8a755af6",
|
||||
"name": "Test Data",
|
||||
"type": "n8n-nodes-testing.testData",
|
||||
"typeVersion": 1,
|
||||
"position": [930, 360]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "microsoftEntraServicePrincipalApi",
|
||||
"mailbox": {
|
||||
"__rl": true,
|
||||
"mode": "id",
|
||||
"value": "={{ $json.mailbox }}"
|
||||
},
|
||||
"resource": "message",
|
||||
"operation": "send",
|
||||
"toRecipients": "[email protected]",
|
||||
"subject": "Hello",
|
||||
"bodyContent": "message description",
|
||||
"additionalFields": {
|
||||
"bodyContentType": "Text"
|
||||
}
|
||||
},
|
||||
"id": "baff6798-0304-4255-bdb0-dd3f2659373e",
|
||||
"name": "Microsoft Outlook",
|
||||
"type": "n8n-nodes-base.microsoftOutlook",
|
||||
"typeVersion": 2,
|
||||
"position": [1040, 360],
|
||||
"onError": "continueRegularOutput",
|
||||
"credentials": {
|
||||
"microsoftEntraServicePrincipalApi": {
|
||||
"id": "spCredId1",
|
||||
"name": "Microsoft Entra Service Principal Test"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Microsoft Outlook": [
|
||||
{
|
||||
"json": {
|
||||
"error": "The mailbox is not valid"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"success": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Test Data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Test Data": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Microsoft Outlook",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { IDataObject, IExecuteFunctions, ILoadOptionsFunctions, INode } from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
import { NodeApiError, NodeOperationError } from 'n8n-workflow';
|
||||
import type { Mock, Mocked } from 'vitest';
|
||||
import { mockDeep } from 'vitest-mock-extended';
|
||||
|
||||
import {
|
||||
downloadAttachments,
|
||||
getMimeContent,
|
||||
getOutlookCredentialType,
|
||||
getSubfolders,
|
||||
microsoftApiRequest,
|
||||
@@ -43,7 +45,7 @@ describe('MicrosoftOutlookV2 - microsoftApiRequest', () => {
|
||||
graphApiBaseUrl: 'https://graph.microsoft.us',
|
||||
});
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages');
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages', 0);
|
||||
|
||||
expect(mockRequestWithAuthentication).toHaveBeenCalledWith(
|
||||
'microsoftOutlookOAuth2Api',
|
||||
@@ -62,7 +64,7 @@ describe('MicrosoftOutlookV2 - microsoftApiRequest', () => {
|
||||
graphApiBaseUrl: '',
|
||||
});
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages');
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages', 0);
|
||||
|
||||
expect(mockRequestWithAuthentication).toHaveBeenCalledWith(
|
||||
'microsoftOutlookOAuth2Api',
|
||||
@@ -78,7 +80,7 @@ describe('MicrosoftOutlookV2 - microsoftApiRequest', () => {
|
||||
oauthTokenData: { access_token: 'test-access-token' },
|
||||
});
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages');
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages', 0);
|
||||
|
||||
expect(mockRequestWithAuthentication).toHaveBeenCalledWith(
|
||||
'microsoftOutlookOAuth2Api',
|
||||
@@ -94,7 +96,7 @@ describe('MicrosoftOutlookV2 - microsoftApiRequest', () => {
|
||||
graphApiBaseUrl: 'https://graph.microsoft.com/',
|
||||
});
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages');
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages', 0);
|
||||
|
||||
expect(mockRequestWithAuthentication).toHaveBeenCalledWith(
|
||||
'microsoftOutlookOAuth2Api',
|
||||
@@ -110,7 +112,7 @@ describe('MicrosoftOutlookV2 - microsoftApiRequest', () => {
|
||||
graphApiBaseUrl: 'https://graph.microsoft.com///',
|
||||
});
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages');
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages', 0);
|
||||
|
||||
expect(mockRequestWithAuthentication).toHaveBeenCalledWith(
|
||||
'microsoftOutlookOAuth2Api',
|
||||
@@ -126,7 +128,7 @@ describe('MicrosoftOutlookV2 - microsoftApiRequest', () => {
|
||||
graphApiBaseUrl: 'https://graph.microsoft.us',
|
||||
});
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages');
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages', 0);
|
||||
|
||||
expect(mockRequestWithAuthentication).toHaveBeenCalledWith(
|
||||
'microsoftOutlookOAuth2Api',
|
||||
@@ -142,7 +144,7 @@ describe('MicrosoftOutlookV2 - microsoftApiRequest', () => {
|
||||
graphApiBaseUrl: 'https://dod-graph.microsoft.us',
|
||||
});
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages');
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages', 0);
|
||||
|
||||
expect(mockRequestWithAuthentication).toHaveBeenCalledWith(
|
||||
'microsoftOutlookOAuth2Api',
|
||||
@@ -158,7 +160,7 @@ describe('MicrosoftOutlookV2 - microsoftApiRequest', () => {
|
||||
graphApiBaseUrl: 'https://microsoftgraph.chinacloudapi.cn',
|
||||
});
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages');
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages', 0);
|
||||
|
||||
expect(mockRequestWithAuthentication).toHaveBeenCalledWith(
|
||||
'microsoftOutlookOAuth2Api',
|
||||
@@ -175,7 +177,7 @@ describe('MicrosoftOutlookV2 - microsoftApiRequest', () => {
|
||||
mockExecuteFunctions.getNodeParameter.mockReturnValue(undefined);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({});
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages');
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages', 0);
|
||||
|
||||
expect(mockExecuteFunctions.getCredentials).toHaveBeenCalledWith('microsoftOutlookOAuth2Api');
|
||||
expect(mockRequestWithAuthentication).toHaveBeenCalledWith(
|
||||
@@ -189,7 +191,7 @@ describe('MicrosoftOutlookV2 - microsoftApiRequest', () => {
|
||||
mockExecuteFunctions.getNodeParameter.mockReturnValue('microsoftOAuth2Api');
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({});
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages');
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages', 0);
|
||||
|
||||
expect(mockExecuteFunctions.getCredentials).toHaveBeenCalledWith('microsoftOAuth2Api');
|
||||
expect(mockRequestWithAuthentication).toHaveBeenCalledWith(
|
||||
@@ -213,7 +215,7 @@ describe('MicrosoftOutlookV2 - microsoftApiRequest', () => {
|
||||
userPrincipalName: '[email protected]',
|
||||
});
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages');
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages', 0);
|
||||
|
||||
expect(mockRequestWithAuthentication).toHaveBeenCalledWith(
|
||||
'microsoftOutlookOAuth2Api',
|
||||
@@ -252,7 +254,7 @@ describe('MicrosoftOutlookV2 - microsoftApiRequest', () => {
|
||||
it('should target /users/{encoded-mailbox} and resolve the SP credential', async () => {
|
||||
setupSP({ mailbox: '[email protected]' });
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages');
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages', 0);
|
||||
|
||||
expect(mockExecuteFunctions.getCredentials).toHaveBeenCalledWith(
|
||||
'microsoftEntraServicePrincipalApi',
|
||||
@@ -272,7 +274,7 @@ describe('MicrosoftOutlookV2 - microsoftApiRequest', () => {
|
||||
// this covers the isResourceLocatorValue(raw) ? raw.value branch directly.
|
||||
setupSP({ mailbox: { __rl: true, mode: 'id', value: '[email protected]' } });
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages');
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages', 0);
|
||||
|
||||
expect(mockRequestWithAuthentication).toHaveBeenCalledWith(
|
||||
'microsoftEntraServicePrincipalApi',
|
||||
@@ -285,7 +287,7 @@ describe('MicrosoftOutlookV2 - microsoftApiRequest', () => {
|
||||
it('should pass a GUID mailbox through verbatim (nothing to percent-encode)', async () => {
|
||||
setupSP({ mailbox: '11111111-1111-1111-1111-111111111111' });
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages');
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages', 0);
|
||||
|
||||
expect(mockRequestWithAuthentication).toHaveBeenCalledWith(
|
||||
'microsoftEntraServicePrincipalApi',
|
||||
@@ -298,7 +300,7 @@ describe('MicrosoftOutlookV2 - microsoftApiRequest', () => {
|
||||
it('should carry a B2B guest (#EXT#) mailbox end-to-end into an encoded /users/{id} uri', async () => {
|
||||
setupSP({ mailbox: 'user_contoso.com#EXT#@tenant.onmicrosoft.com' });
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages');
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages', 0);
|
||||
|
||||
expect(mockRequestWithAuthentication).toHaveBeenCalledWith(
|
||||
'microsoftEntraServicePrincipalApi',
|
||||
@@ -311,7 +313,7 @@ describe('MicrosoftOutlookV2 - microsoftApiRequest', () => {
|
||||
it('should carry a "^"/"!" mailbox end-to-end into an encoded /users/{id} uri', async () => {
|
||||
setupSP({ mailbox: 'joe^[email protected]' });
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages');
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages', 0);
|
||||
|
||||
expect(mockRequestWithAuthentication).toHaveBeenCalledWith(
|
||||
'microsoftEntraServicePrincipalApi',
|
||||
@@ -324,7 +326,7 @@ describe('MicrosoftOutlookV2 - microsoftApiRequest', () => {
|
||||
it('should target the sovereign cloud base for the SP mailbox', async () => {
|
||||
setupSP({ mailbox: '[email protected]', graphApiBaseUrl: 'https://graph.microsoft.us' });
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages');
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages', 0);
|
||||
|
||||
expect(mockRequestWithAuthentication).toHaveBeenCalledWith(
|
||||
'microsoftEntraServicePrincipalApi',
|
||||
@@ -339,7 +341,7 @@ describe('MicrosoftOutlookV2 - microsoftApiRequest', () => {
|
||||
const nextLink =
|
||||
'https://graph.microsoft.com/v1.0/users/user%40example.com/messages?$skip=10&$top=10';
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages', {}, {}, nextLink);
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages', 0, {}, {}, nextLink);
|
||||
|
||||
const callArgs = mockRequestWithAuthentication.mock.calls[0][1] as IDataObject;
|
||||
expect(callArgs.uri).toBe(nextLink);
|
||||
@@ -351,10 +353,10 @@ describe('MicrosoftOutlookV2 - microsoftApiRequest', () => {
|
||||
setupSP({ mailbox: '' });
|
||||
|
||||
await expect(
|
||||
microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages'),
|
||||
microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages', 0),
|
||||
).rejects.toThrow(NodeOperationError);
|
||||
await expect(
|
||||
microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages'),
|
||||
microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages', 0),
|
||||
).rejects.toThrow('A mailbox is required for the Service Principal');
|
||||
expect(mockRequestWithAuthentication).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -364,7 +366,7 @@ describe('MicrosoftOutlookV2 - microsoftApiRequest', () => {
|
||||
setupSP({ mailbox: ' ' });
|
||||
|
||||
await expect(
|
||||
microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages'),
|
||||
microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages', 0),
|
||||
).rejects.toThrow('A mailbox is required for the Service Principal');
|
||||
expect(mockRequestWithAuthentication).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -375,10 +377,140 @@ describe('MicrosoftOutlookV2 - microsoftApiRequest', () => {
|
||||
setupSP({ mailbox: 'contoso.com' });
|
||||
|
||||
await expect(
|
||||
microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages'),
|
||||
microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/messages', 0),
|
||||
).rejects.toThrow('The mailbox is not valid');
|
||||
expect(mockRequestWithAuthentication).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// The mailbox RLC accepts expressions, so execute call sites pass the loop index
|
||||
// and the transport must read the mailbox at exactly that index. Reuses setupSP
|
||||
// and only swaps in an index-aware mailbox read.
|
||||
const setupSPPerItem = () => {
|
||||
setupSP();
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation(
|
||||
(name: string, itemIndex?: number) => {
|
||||
if (name === 'authentication') return 'microsoftEntraServicePrincipalApi';
|
||||
if (name === 'mailbox')
|
||||
return itemIndex === 1 ? '[email protected]' : '[email protected]';
|
||||
return undefined;
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
it('should resolve the mailbox at the passed itemIndex', async () => {
|
||||
setupSPPerItem();
|
||||
|
||||
await microsoftApiRequest.call(
|
||||
mockExecuteFunctions,
|
||||
'GET',
|
||||
'/messages',
|
||||
0,
|
||||
{},
|
||||
{},
|
||||
undefined,
|
||||
{},
|
||||
{ json: true },
|
||||
);
|
||||
await microsoftApiRequest.call(
|
||||
mockExecuteFunctions,
|
||||
'GET',
|
||||
'/messages',
|
||||
1,
|
||||
{},
|
||||
{},
|
||||
undefined,
|
||||
{},
|
||||
{ json: true },
|
||||
);
|
||||
|
||||
expect(mockRequestWithAuthentication).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'microsoftEntraServicePrincipalApi',
|
||||
expect.objectContaining({
|
||||
uri: 'https://graph.microsoft.com/v1.0/users/user1%40example.com/messages',
|
||||
}),
|
||||
);
|
||||
expect(mockRequestWithAuthentication).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'microsoftEntraServicePrincipalApi',
|
||||
expect.objectContaining({
|
||||
uri: 'https://graph.microsoft.com/v1.0/users/user2%40example.com/messages',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should attribute a Graph error to the passed itemIndex (prepareApiError path)', async () => {
|
||||
setupSP();
|
||||
// "bad request" message + parseable description route the error through
|
||||
// prepareApiError, which must receive the forwarded index.
|
||||
mockRequestWithAuthentication.mockRejectedValue(
|
||||
Object.assign(new Error('bad request'), {
|
||||
description: '400 - {"error":{"code":"ErrorInvalidRequest","message":"broken"}} - broken',
|
||||
}),
|
||||
);
|
||||
|
||||
let caught: unknown;
|
||||
try {
|
||||
await microsoftApiRequest.call(
|
||||
mockExecuteFunctions,
|
||||
'GET',
|
||||
'/messages',
|
||||
2,
|
||||
{},
|
||||
{},
|
||||
undefined,
|
||||
{},
|
||||
{ json: true },
|
||||
);
|
||||
} catch (error) {
|
||||
caught = error;
|
||||
}
|
||||
|
||||
expect(caught).toBeInstanceOf(NodeApiError);
|
||||
expect((caught as NodeApiError).context.itemIndex).toBe(2);
|
||||
});
|
||||
|
||||
it('should download attachments from the mailbox at the passed itemIndex', async () => {
|
||||
setupSPPerItem();
|
||||
mockRequestWithAuthentication
|
||||
.mockResolvedValueOnce({
|
||||
value: [{ id: 'att1', name: 'file.txt', contentType: 'text/plain' }],
|
||||
})
|
||||
.mockResolvedValueOnce({ body: 'DATA', headers: {} });
|
||||
|
||||
await downloadAttachments.call(
|
||||
mockExecuteFunctions,
|
||||
{ id: 'MSG1', hasAttachments: true },
|
||||
'attachment_',
|
||||
1,
|
||||
);
|
||||
|
||||
const uris = mockRequestWithAuthentication.mock.calls.map(
|
||||
(call) => (call[1] as IDataObject).uri,
|
||||
);
|
||||
expect(uris).toEqual([
|
||||
'https://graph.microsoft.com/v1.0/users/user2%40example.com/messages/MSG1/attachments',
|
||||
'https://graph.microsoft.com/v1.0/users/user2%40example.com/messages/MSG1/attachments/att1/$value',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should fetch MIME content from the mailbox at the passed itemIndex', async () => {
|
||||
setupSPPerItem();
|
||||
mockRequestWithAuthentication.mockResolvedValue({
|
||||
body: 'MIME',
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
headers: { 'content-type': 'message/rfc822' },
|
||||
});
|
||||
|
||||
await getMimeContent.call(mockExecuteFunctions, 'MSG1', 'data', 1);
|
||||
|
||||
expect(mockRequestWithAuthentication).toHaveBeenCalledWith(
|
||||
'microsoftEntraServicePrincipalApi',
|
||||
expect.objectContaining({
|
||||
uri: 'https://graph.microsoft.com/v1.0/users/user2%40example.com/messages/MSG1/$value',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -401,7 +533,7 @@ describe('MicrosoftOutlookV2 - getSubfolders', () => {
|
||||
{ id: 'folder2', displayName: 'Sent Items', childFolderCount: 0 },
|
||||
];
|
||||
|
||||
const result = await getSubfolders.call(mockLoadOptionsFunctions, folders);
|
||||
const result = await getSubfolders.call(mockLoadOptionsFunctions, folders, 0);
|
||||
|
||||
expect(
|
||||
mockLoadOptionsFunctions.helpers.requestWithAuthentication as Mock,
|
||||
@@ -422,7 +554,7 @@ describe('MicrosoftOutlookV2 - getSubfolders', () => {
|
||||
value: [{ id: 'sub2', displayName: 'Projects', childFolderCount: 0 }],
|
||||
});
|
||||
|
||||
const result = await getSubfolders.call(mockLoadOptionsFunctions, folders);
|
||||
const result = await getSubfolders.call(mockLoadOptionsFunctions, folders, 0);
|
||||
|
||||
expect(
|
||||
mockLoadOptionsFunctions.helpers.requestWithAuthentication as Mock,
|
||||
@@ -445,7 +577,7 @@ describe('MicrosoftOutlookV2 - getSubfolders', () => {
|
||||
value: [{ id: 'q2', displayName: 'Q2', childFolderCount: 0 }],
|
||||
});
|
||||
|
||||
const result = await getSubfolders.call(mockLoadOptionsFunctions, folders, true);
|
||||
const result = await getSubfolders.call(mockLoadOptionsFunctions, folders, 0, true);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ id: 'inbox', displayName: 'Inbox', childFolderCount: 1 },
|
||||
@@ -461,7 +593,7 @@ describe('MicrosoftOutlookV2 - getSubfolders', () => {
|
||||
value: [{ id: 'work', displayName: 'Work', childFolderCount: 0 }],
|
||||
});
|
||||
|
||||
const result = await getSubfolders.call(mockLoadOptionsFunctions, folders);
|
||||
const result = await getSubfolders.call(mockLoadOptionsFunctions, folders, 0);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ id: 'inbox', displayName: 'Inbox', childFolderCount: 1 },
|
||||
|
||||
@@ -57,7 +57,8 @@ export async function getPollResponse(
|
||||
const results = await Promise.all(
|
||||
endpoints.map(
|
||||
async (endpoint) =>
|
||||
await microsoftApiRequestAllItems.call(this, 'value', 'GET', endpoint, undefined, {
|
||||
// Poll context: 0 is the transport's fallback read, not an item index.
|
||||
await microsoftApiRequestAllItems.call(this, 'value', 'GET', endpoint, 0, undefined, {
|
||||
...qs,
|
||||
}),
|
||||
),
|
||||
@@ -71,7 +72,8 @@ export async function getPollResponse(
|
||||
const results = await Promise.all(
|
||||
endpoints.map(
|
||||
async (endpoint) =>
|
||||
await microsoftApiRequest.call(this, 'GET', endpoint, undefined, { ...qs }),
|
||||
// Poll context: 0 is the transport's fallback read, not an item index.
|
||||
await microsoftApiRequest.call(this, 'GET', endpoint, 0, undefined, { ...qs }),
|
||||
),
|
||||
);
|
||||
responseData = results.flatMap((result) => (result.value as IDataObject[]) ?? []).slice(0, 1);
|
||||
@@ -85,7 +87,13 @@ export async function getPollResponse(
|
||||
|
||||
if (options.downloadAttachments) {
|
||||
const prefix = (options.attachmentsPrefix as string) || 'attachment_';
|
||||
executionData = await downloadAttachments.call(this, responseData as IDataObject[], prefix);
|
||||
// Poll context: 0 is the transport's fallback read, not an item index.
|
||||
executionData = await downloadAttachments.call(
|
||||
this,
|
||||
responseData as IDataObject[],
|
||||
prefix,
|
||||
0,
|
||||
);
|
||||
} else {
|
||||
executionData = this.helpers.returnJsonArray(responseData as IDataObject[]);
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
...additionalFields,
|
||||
};
|
||||
|
||||
const responseData = await microsoftApiRequest.call(this, 'POST', endpoint, body);
|
||||
const responseData = await microsoftApiRequest.call(this, 'POST', endpoint, index, body);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||
|
||||
@@ -21,7 +21,7 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
await microsoftApiRequest.call(this, 'DELETE', `/calendars/${calendarId}`);
|
||||
await microsoftApiRequest.call(this, 'DELETE', `/calendars/${calendarId}`, index);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ success: true }),
|
||||
|
||||
@@ -27,6 +27,7 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
this,
|
||||
'GET',
|
||||
`/calendars/${calendarId}`,
|
||||
index,
|
||||
undefined,
|
||||
qs,
|
||||
);
|
||||
|
||||
@@ -62,12 +62,13 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
'value',
|
||||
'GET',
|
||||
endpoint,
|
||||
index,
|
||||
undefined,
|
||||
qs,
|
||||
);
|
||||
} else {
|
||||
qs.$top = this.getNodeParameter('limit', index);
|
||||
responseData = await microsoftApiRequest.call(this, 'GET', endpoint, undefined, qs);
|
||||
responseData = await microsoftApiRequest.call(this, 'GET', endpoint, index, undefined, qs);
|
||||
responseData = responseData.value;
|
||||
}
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
...updateFields,
|
||||
};
|
||||
|
||||
const responseData = await microsoftApiRequest.call(this, 'PATCH', endpoint, body);
|
||||
const responseData = await microsoftApiRequest.call(this, 'PATCH', endpoint, index, body);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||
|
||||
@@ -53,7 +53,7 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
body.surname = surname;
|
||||
}
|
||||
|
||||
const responseData = await microsoftApiRequest.call(this, 'POST', '/contacts', body);
|
||||
const responseData = await microsoftApiRequest.call(this, 'POST', '/contacts', index, body);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||
|
||||
@@ -20,7 +20,7 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
const contactId = this.getNodeParameter('contactId', index, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
await microsoftApiRequest.call(this, 'DELETE', `/contacts/${contactId}`);
|
||||
await microsoftApiRequest.call(this, 'DELETE', `/contacts/${contactId}`, index);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ success: true }),
|
||||
|
||||
@@ -74,6 +74,7 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
this,
|
||||
'GET',
|
||||
`/contacts/${contactId}`,
|
||||
index,
|
||||
undefined,
|
||||
qs,
|
||||
);
|
||||
|
||||
@@ -121,12 +121,13 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
'value',
|
||||
'GET',
|
||||
endpoint,
|
||||
index,
|
||||
undefined,
|
||||
qs,
|
||||
);
|
||||
} else {
|
||||
qs.$top = this.getNodeParameter('limit', index);
|
||||
responseData = await microsoftApiRequest.call(this, 'GET', endpoint, undefined, qs);
|
||||
responseData = await microsoftApiRequest.call(this, 'GET', endpoint, index, undefined, qs);
|
||||
responseData = responseData.value;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
this,
|
||||
'PATCH',
|
||||
`/contacts/${contactId}`,
|
||||
index,
|
||||
body,
|
||||
);
|
||||
|
||||
|
||||
@@ -244,7 +244,7 @@ export async function execute(this: IExecuteFunctions, index: number, _: INodeEx
|
||||
);
|
||||
}
|
||||
|
||||
const responseData = await microsoftApiRequest.call(this, 'POST', '/messages', body, {});
|
||||
const responseData = await microsoftApiRequest.call(this, 'POST', '/messages', index, body, {});
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||
|
||||
@@ -20,7 +20,7 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
const draftId = this.getNodeParameter('draftId', index, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
await microsoftApiRequest.call(this, 'DELETE', `/messages/${draftId}`);
|
||||
await microsoftApiRequest.call(this, 'DELETE', `/messages/${draftId}`, index);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ success: true }),
|
||||
|
||||
@@ -107,7 +107,14 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
'id,conversationId,subject,bodyPreview,from,toRecipients,categories,hasAttachments';
|
||||
}
|
||||
|
||||
responseData = await microsoftApiRequest.call(this, 'GET', `/messages/${draftId}`, undefined, qs);
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/messages/${draftId}`,
|
||||
index,
|
||||
undefined,
|
||||
qs,
|
||||
);
|
||||
|
||||
if (output === 'simple') {
|
||||
responseData = simplifyOutputMessages([responseData as IDataObject]);
|
||||
@@ -117,7 +124,12 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
|
||||
if (options.downloadAttachments) {
|
||||
const prefix = (options.attachmentsPrefix as string) || 'attachment_';
|
||||
executionData = await downloadAttachments.call(this, responseData as IDataObject, prefix);
|
||||
executionData = await downloadAttachments.call(
|
||||
this,
|
||||
responseData as IDataObject,
|
||||
prefix,
|
||||
index,
|
||||
);
|
||||
} else {
|
||||
executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||
|
||||
@@ -37,13 +37,13 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
.filter((email) => email);
|
||||
|
||||
if (recipients.length !== 0) {
|
||||
await microsoftApiRequest.call(this, 'PATCH', `/messages/${draftId}`, {
|
||||
await microsoftApiRequest.call(this, 'PATCH', `/messages/${draftId}`, index, {
|
||||
toRecipients: recipients.map((recipient: string) => makeRecipient(recipient)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await microsoftApiRequest.call(this, 'POST', `/messages/${draftId}/send`);
|
||||
await microsoftApiRequest.call(this, 'POST', `/messages/${draftId}/send`, index);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ success: true }),
|
||||
|
||||
@@ -195,6 +195,7 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
this,
|
||||
'PATCH',
|
||||
`/messages/${draftId}`,
|
||||
index,
|
||||
body,
|
||||
{},
|
||||
);
|
||||
|
||||
@@ -286,7 +286,7 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
...additionalFields,
|
||||
};
|
||||
|
||||
const responseData = await microsoftApiRequest.call(this, 'POST', endpoint, body);
|
||||
const responseData = await microsoftApiRequest.call(this, 'POST', endpoint, index, body);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||
|
||||
@@ -24,7 +24,7 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
}) as string,
|
||||
);
|
||||
|
||||
await microsoftApiRequest.call(this, 'DELETE', `/calendar/events/${eventId}`);
|
||||
await microsoftApiRequest.call(this, 'DELETE', `/calendar/events/${eventId}`, index);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ success: true }),
|
||||
|
||||
@@ -75,7 +75,7 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
|
||||
const endpoint = `/calendar/events/${eventId}`;
|
||||
|
||||
const responseData = await microsoftApiRequest.call(this, 'GET', endpoint, undefined, qs);
|
||||
const responseData = await microsoftApiRequest.call(this, 'GET', endpoint, index, undefined, qs);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject[]),
|
||||
|
||||
@@ -166,7 +166,7 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
const fromAllCalendars = this.getNodeParameter('fromAllCalendars', index) as boolean;
|
||||
|
||||
if (fromAllCalendars) {
|
||||
const response = await microsoftApiRequest.call(this, 'GET', '/calendars', undefined, {
|
||||
const response = await microsoftApiRequest.call(this, 'GET', '/calendars', index, undefined, {
|
||||
$select: 'id',
|
||||
});
|
||||
for (const calendar of response.value) {
|
||||
@@ -192,6 +192,7 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
'value',
|
||||
'GET',
|
||||
endpoint,
|
||||
index,
|
||||
undefined,
|
||||
qs,
|
||||
);
|
||||
@@ -201,7 +202,7 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
|
||||
if (qs.$top <= 0) break;
|
||||
|
||||
const response = await microsoftApiRequest.call(this, 'GET', endpoint, undefined, qs);
|
||||
const response = await microsoftApiRequest.call(this, 'GET', endpoint, index, undefined, qs);
|
||||
responseData.push(...response.value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,7 +280,7 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
|
||||
const endpoint = `/calendar/events/${eventId}`;
|
||||
|
||||
const responseData = await microsoftApiRequest.call(this, 'PATCH', endpoint, body);
|
||||
const responseData = await microsoftApiRequest.call(this, 'PATCH', endpoint, index, body);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||
|
||||
@@ -56,7 +56,7 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
endpoint = '/mailFolders';
|
||||
}
|
||||
|
||||
const responseData = await microsoftApiRequest.call(this, 'POST', endpoint, body);
|
||||
const responseData = await microsoftApiRequest.call(this, 'POST', endpoint, index, body);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject[]),
|
||||
|
||||
@@ -24,7 +24,7 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
}) as string,
|
||||
);
|
||||
|
||||
await microsoftApiRequest.call(this, 'DELETE', `/mailFolders/${folderId}`);
|
||||
await microsoftApiRequest.call(this, 'DELETE', `/mailFolders/${folderId}`, index);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ success: true }),
|
||||
|
||||
@@ -58,6 +58,7 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
this,
|
||||
'GET',
|
||||
`/mailFolders/${folderId}`,
|
||||
index,
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
|
||||
@@ -93,15 +93,23 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
}
|
||||
|
||||
if (returnAll) {
|
||||
responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', endpoint, {}, qs);
|
||||
responseData = await microsoftApiRequestAllItems.call(
|
||||
this,
|
||||
'value',
|
||||
'GET',
|
||||
endpoint,
|
||||
index,
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
} else {
|
||||
qs.$top = this.getNodeParameter('limit', index);
|
||||
responseData = await microsoftApiRequest.call(this, 'GET', endpoint, {}, qs);
|
||||
responseData = await microsoftApiRequest.call(this, 'GET', endpoint, index, {}, qs);
|
||||
responseData = responseData.value;
|
||||
}
|
||||
|
||||
if (options.includeChildFolders) {
|
||||
responseData = await getSubfolders.call(this, responseData as IDataObject[]);
|
||||
responseData = await getSubfolders.call(this, responseData as IDataObject[], index);
|
||||
}
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
|
||||
@@ -35,9 +35,13 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
);
|
||||
const displayName = this.getNodeParameter('displayName', index, undefined) as string;
|
||||
|
||||
const responseData = await microsoftApiRequest.call(this, 'PATCH', `/mailFolders/${folderId}`, {
|
||||
displayName,
|
||||
});
|
||||
const responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'PATCH',
|
||||
`/mailFolders/${folderId}`,
|
||||
index,
|
||||
{ displayName },
|
||||
);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||
|
||||
+8
-2
@@ -274,12 +274,13 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
'value',
|
||||
'GET',
|
||||
endpoint,
|
||||
index,
|
||||
undefined,
|
||||
qs,
|
||||
);
|
||||
} else {
|
||||
qs.$top = this.getNodeParameter('limit', index);
|
||||
responseData = await microsoftApiRequest.call(this, 'GET', endpoint, undefined, qs);
|
||||
responseData = await microsoftApiRequest.call(this, 'GET', endpoint, index, undefined, qs);
|
||||
responseData = responseData.value;
|
||||
}
|
||||
|
||||
@@ -291,7 +292,12 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
|
||||
if (options.downloadAttachments) {
|
||||
const prefix = (options.attachmentsPrefix as string) || 'attachment_';
|
||||
executionData = await downloadAttachments.call(this, responseData as IDataObject, prefix);
|
||||
executionData = await downloadAttachments.call(
|
||||
this,
|
||||
responseData as IDataObject,
|
||||
prefix,
|
||||
index,
|
||||
);
|
||||
} else {
|
||||
executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject[]),
|
||||
|
||||
@@ -20,7 +20,7 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
const messageId = this.getNodeParameter('messageId', index, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
await microsoftApiRequest.call(this, 'DELETE', `/messages/${messageId}`);
|
||||
await microsoftApiRequest.call(this, 'DELETE', `/messages/${messageId}`, index);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ success: true }),
|
||||
|
||||
@@ -142,6 +142,7 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
this,
|
||||
'GET',
|
||||
`/messages/${messageId}`,
|
||||
index,
|
||||
undefined,
|
||||
qs,
|
||||
);
|
||||
@@ -154,7 +155,12 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
|
||||
if (options.downloadAttachments) {
|
||||
const prefix = (options.attachmentsPrefix as string) || 'attachment_';
|
||||
executionData = await downloadAttachments.call(this, responseData as IDataObject, prefix);
|
||||
executionData = await downloadAttachments.call(
|
||||
this,
|
||||
responseData as IDataObject,
|
||||
prefix,
|
||||
index,
|
||||
);
|
||||
} else {
|
||||
executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||
@@ -170,6 +176,7 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
this,
|
||||
messageId,
|
||||
binaryPropertyName as string,
|
||||
index,
|
||||
outputFileName as string,
|
||||
);
|
||||
|
||||
|
||||
@@ -284,12 +284,13 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
'value',
|
||||
'GET',
|
||||
endpoint,
|
||||
index,
|
||||
undefined,
|
||||
qs,
|
||||
);
|
||||
} else {
|
||||
qs.$top = this.getNodeParameter('limit', index);
|
||||
responseData = await microsoftApiRequest.call(this, 'GET', endpoint, undefined, qs);
|
||||
responseData = await microsoftApiRequest.call(this, 'GET', endpoint, index, undefined, qs);
|
||||
responseData = responseData.value;
|
||||
}
|
||||
|
||||
@@ -301,7 +302,12 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
|
||||
if (options.downloadAttachments) {
|
||||
const prefix = (options.attachmentsPrefix as string) || 'attachment_';
|
||||
executionData = await downloadAttachments.call(this, responseData as IDataObject, prefix);
|
||||
executionData = await downloadAttachments.call(
|
||||
this,
|
||||
responseData as IDataObject,
|
||||
prefix,
|
||||
index,
|
||||
);
|
||||
} else {
|
||||
executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject[]),
|
||||
|
||||
@@ -32,7 +32,7 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
destinationId,
|
||||
};
|
||||
|
||||
await microsoftApiRequest.call(this, 'POST', `/messages/${messageId}/move`, body);
|
||||
await microsoftApiRequest.call(this, 'POST', `/messages/${messageId}/move`, index, body);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ success: true }),
|
||||
|
||||
@@ -248,6 +248,7 @@ export async function execute(this: IExecuteFunctions, index: number, _: INodeEx
|
||||
this,
|
||||
'POST',
|
||||
`/messages/${messageId}/${action}`,
|
||||
index,
|
||||
body,
|
||||
);
|
||||
|
||||
@@ -283,6 +284,7 @@ export async function execute(this: IExecuteFunctions, index: number, _: INodeEx
|
||||
this,
|
||||
'POST',
|
||||
`/messages/${responseData.id}/attachments`,
|
||||
index,
|
||||
attachment,
|
||||
{},
|
||||
);
|
||||
@@ -290,7 +292,7 @@ export async function execute(this: IExecuteFunctions, index: number, _: INodeEx
|
||||
}
|
||||
|
||||
if (!saveAsDraft) {
|
||||
await microsoftApiRequest.call(this, 'POST', `/messages/${responseData.id}/send`);
|
||||
await microsoftApiRequest.call(this, 'POST', `/messages/${responseData.id}/send`, index);
|
||||
}
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
|
||||
@@ -258,7 +258,7 @@ export async function execute(this: IExecuteFunctions, index: number, _: INodeEx
|
||||
saveToSentItems,
|
||||
};
|
||||
|
||||
await microsoftApiRequest.call(this, 'POST', '/sendMail', body, {});
|
||||
await microsoftApiRequest.call(this, 'POST', '/sendMail', index, body, {});
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ success: true }),
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ export async function execute(this: IExecuteFunctions, index: number, items: INo
|
||||
|
||||
const body: IDataObject = { message };
|
||||
|
||||
await microsoftApiRequest.call(this, 'POST', '/sendMail', body);
|
||||
await microsoftApiRequest.call(this, 'POST', '/sendMail', index, body);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
@@ -194,6 +194,7 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
this,
|
||||
'POST',
|
||||
`/messages/${messageId}/move`,
|
||||
index,
|
||||
body,
|
||||
);
|
||||
|
||||
@@ -215,7 +216,14 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
throw new NodeOperationError(this.getNode(), 'No fields to update got specified');
|
||||
}
|
||||
|
||||
responseData = await microsoftApiRequest.call(this, 'PATCH', `/messages/${messageId}`, body, {});
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'PATCH',
|
||||
`/messages/${messageId}`,
|
||||
index,
|
||||
body,
|
||||
{},
|
||||
);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||
|
||||
+2
@@ -91,6 +91,7 @@ export async function execute(this: IExecuteFunctions, index: number, _: INodeEx
|
||||
this,
|
||||
'POST',
|
||||
`/messages/${messageId}/attachments/createUploadSession`,
|
||||
index,
|
||||
body,
|
||||
);
|
||||
const uploadUrl = responseData.uploadUrl;
|
||||
@@ -129,6 +130,7 @@ export async function execute(this: IExecuteFunctions, index: number, _: INodeEx
|
||||
this,
|
||||
'POST',
|
||||
`/messages/${messageId}/attachments`,
|
||||
index,
|
||||
body,
|
||||
{},
|
||||
);
|
||||
|
||||
+2
@@ -43,6 +43,7 @@ export async function execute(this: IExecuteFunctions, index: number, items: INo
|
||||
this,
|
||||
'GET',
|
||||
`/messages/${messageId}/attachments/${attachmentId}`,
|
||||
index,
|
||||
undefined,
|
||||
{ $select: 'id,name,contentType' },
|
||||
);
|
||||
@@ -57,6 +58,7 @@ export async function execute(this: IExecuteFunctions, index: number, items: INo
|
||||
this,
|
||||
'GET',
|
||||
`/messages/${messageId}/attachments/${attachmentId}/$value`,
|
||||
index,
|
||||
undefined,
|
||||
{},
|
||||
undefined,
|
||||
|
||||
+1
@@ -83,6 +83,7 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
this,
|
||||
'GET',
|
||||
`/messages/${messageId}/attachments/${attachmentId}`,
|
||||
index,
|
||||
undefined,
|
||||
qs,
|
||||
);
|
||||
|
||||
+2
-1
@@ -84,12 +84,13 @@ export async function execute(this: IExecuteFunctions, index: number) {
|
||||
'value',
|
||||
'GET',
|
||||
endpoint,
|
||||
index,
|
||||
undefined,
|
||||
qs,
|
||||
);
|
||||
} else {
|
||||
qs.$top = this.getNodeParameter('limit', index);
|
||||
responseData = await microsoftApiRequest.call(this, 'GET', endpoint, undefined, qs);
|
||||
responseData = await microsoftApiRequest.call(this, 'GET', endpoint, index, undefined, qs);
|
||||
responseData = responseData.value;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { IExecuteFunctions, INodeExecutionData, JsonObject } from 'n8n-workflow';
|
||||
import { NodeApiError, NodeOperationError, SEND_AND_WAIT_OPERATION } from 'n8n-workflow';
|
||||
import { NodeOperationError, SEND_AND_WAIT_OPERATION } from 'n8n-workflow';
|
||||
|
||||
import * as calendar from './calendar';
|
||||
import * as contact from './contact';
|
||||
@@ -11,6 +11,7 @@ import * as message from './message';
|
||||
import * as messageAttachment from './messageAttachment';
|
||||
import type { MicrosoftOutlook } from './node.type';
|
||||
import { configureWaitTillDate } from '../../../../../utils/sendAndWait/configureWaitTillDate.util';
|
||||
import { stampItemIndexOnError } from '../../../GenericFunctions';
|
||||
|
||||
export async function router(this: IExecuteFunctions) {
|
||||
const items = this.getInputData();
|
||||
@@ -90,14 +91,8 @@ export async function router(this: IExecuteFunctions) {
|
||||
returnData.push(...executionErrorData);
|
||||
continue;
|
||||
}
|
||||
//NodeApiError will be missing the itemIndex, add it
|
||||
if (error instanceof NodeApiError && error?.context?.itemIndex === undefined) {
|
||||
if (error.context === undefined) {
|
||||
error.context = {};
|
||||
}
|
||||
error.context.itemIndex = i;
|
||||
}
|
||||
throw error;
|
||||
// A NodeError from the transport may be missing the itemIndex, add it
|
||||
throw stampItemIndexOnError(error, i);
|
||||
}
|
||||
}
|
||||
return [returnData];
|
||||
|
||||
@@ -17,10 +17,10 @@ export const mailboxRLC: INodeProperties = {
|
||||
type: 'resourceLocator',
|
||||
default: { mode: 'id', value: '' },
|
||||
required: true,
|
||||
// Resolved once per run (from the first item) and reused for every item, so it accepts a fixed
|
||||
// value or an expression; for per-item targeting, feed a single item or use Loop Over Items.
|
||||
// Shared with the trigger: the action node evaluates this per input item; the
|
||||
// trigger resolves it once per poll.
|
||||
description:
|
||||
'The mailbox the Service Principal should act on. Applies to the whole node (every item in the execution).',
|
||||
'The mailbox the Service Principal should act on. In the action node this is evaluated per input item (an expression can target a different mailbox per item); the trigger resolves it once.',
|
||||
modes: [
|
||||
{
|
||||
displayName: 'By ID',
|
||||
|
||||
@@ -3,6 +3,9 @@ import type { IDataObject, ILoadOptionsFunctions, INodeListSearchResult } from '
|
||||
import { encodeOutlookId } from '../helpers/utils';
|
||||
import { getSubfolders, microsoftApiRequest } from '../transport';
|
||||
|
||||
// listSearch context throughout this file: the transport's trailing `0` is its
|
||||
// fallback read (getNodeParameter's 2nd arg here is a fallback, not an item index).
|
||||
|
||||
async function search(
|
||||
this: ILoadOptionsFunctions,
|
||||
resource: string,
|
||||
@@ -17,6 +20,7 @@ async function search(
|
||||
this,
|
||||
'GET',
|
||||
'',
|
||||
0,
|
||||
undefined,
|
||||
undefined,
|
||||
paginationToken, // paginationToken contains the full URL
|
||||
@@ -32,7 +36,7 @@ async function search(
|
||||
qs.$filter = `contains(${nameProperty}, '${filterValue}')`;
|
||||
}
|
||||
|
||||
response = await microsoftApiRequest.call(this, 'GET', resource, undefined, qs);
|
||||
response = await microsoftApiRequest.call(this, 'GET', resource, 0, undefined, qs);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -74,6 +78,7 @@ export async function searchDrafts(
|
||||
this,
|
||||
'GET',
|
||||
'',
|
||||
0,
|
||||
undefined,
|
||||
undefined,
|
||||
paginationToken, // paginationToken contains the full URL
|
||||
@@ -90,7 +95,7 @@ export async function searchDrafts(
|
||||
qs.$filter += ` AND contains(${'subject'}, '${filterValue}')`;
|
||||
}
|
||||
|
||||
response = await microsoftApiRequest.call(this, 'GET', '/messages', undefined, qs);
|
||||
response = await microsoftApiRequest.call(this, 'GET', '/messages', 0, undefined, qs);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -117,6 +122,7 @@ export async function searchMessages(
|
||||
this,
|
||||
'GET',
|
||||
'',
|
||||
0,
|
||||
undefined,
|
||||
undefined,
|
||||
paginationToken, // paginationToken contains the full URL
|
||||
@@ -132,7 +138,7 @@ export async function searchMessages(
|
||||
qs.$filter = `contains(${'subject'}, '${filterValue}')`;
|
||||
}
|
||||
|
||||
response = await microsoftApiRequest.call(this, 'GET', '/messages', undefined, qs);
|
||||
response = await microsoftApiRequest.call(this, 'GET', '/messages', 0, undefined, qs);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -163,6 +169,7 @@ export async function searchEvents(
|
||||
this,
|
||||
'GET',
|
||||
'',
|
||||
0,
|
||||
undefined,
|
||||
undefined,
|
||||
paginationToken, // paginationToken contains the full URL
|
||||
@@ -182,6 +189,7 @@ export async function searchEvents(
|
||||
this,
|
||||
'GET',
|
||||
`/calendars/${calendarId}/events`,
|
||||
0,
|
||||
undefined,
|
||||
qs,
|
||||
);
|
||||
@@ -211,6 +219,7 @@ export async function searchFolders(
|
||||
this,
|
||||
'GET',
|
||||
'',
|
||||
0,
|
||||
undefined,
|
||||
undefined,
|
||||
paginationToken, // paginationToken contains the full URL
|
||||
@@ -220,10 +229,10 @@ export async function searchFolders(
|
||||
$top: 100,
|
||||
};
|
||||
|
||||
response = await microsoftApiRequest.call(this, 'GET', '/mailFolders', undefined, qs);
|
||||
response = await microsoftApiRequest.call(this, 'GET', '/mailFolders', 0, undefined, qs);
|
||||
}
|
||||
|
||||
let folders = await getSubfolders.call(this, response.value as IDataObject[], true);
|
||||
let folders = await getSubfolders.call(this, response.value as IDataObject[], 0, true);
|
||||
|
||||
if (filter) {
|
||||
filter = filter.toLowerCase();
|
||||
@@ -259,6 +268,7 @@ export async function searchAttachments(
|
||||
this,
|
||||
'GET',
|
||||
'',
|
||||
0,
|
||||
undefined,
|
||||
undefined,
|
||||
paginationToken, // paginationToken contains the full URL
|
||||
@@ -273,6 +283,7 @@ export async function searchAttachments(
|
||||
this,
|
||||
'GET',
|
||||
`/messages/${messageId}/attachments`,
|
||||
0,
|
||||
undefined,
|
||||
qs,
|
||||
);
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { ILoadOptionsFunctions, INodePropertyOptions } from 'n8n-workflow';
|
||||
|
||||
import { getSubfolders, microsoftApiRequestAllItems } from '../transport';
|
||||
|
||||
// loadOptions context throughout this file: the transport's trailing `0` is its
|
||||
// fallback read (getNodeParameter's 2nd arg here is a fallback, not an item index).
|
||||
|
||||
export async function getCategoriesNames(
|
||||
this: ILoadOptionsFunctions,
|
||||
): Promise<INodePropertyOptions[]> {
|
||||
@@ -11,6 +14,7 @@ export async function getCategoriesNames(
|
||||
'value',
|
||||
'GET',
|
||||
'/outlook/masterCategories',
|
||||
0,
|
||||
);
|
||||
for (const category of categories) {
|
||||
returnData.push({
|
||||
@@ -23,8 +27,15 @@ export async function getCategoriesNames(
|
||||
|
||||
export async function getFolders(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const response = await microsoftApiRequestAllItems.call(this, 'value', 'GET', '/mailFolders', {});
|
||||
const folders = await getSubfolders.call(this, response, true);
|
||||
const response = await microsoftApiRequestAllItems.call(
|
||||
this,
|
||||
'value',
|
||||
'GET',
|
||||
'/mailFolders',
|
||||
0,
|
||||
{},
|
||||
);
|
||||
const folders = await getSubfolders.call(this, response, 0, true);
|
||||
for (const folder of folders) {
|
||||
returnData.push({
|
||||
name: folder.displayName as string,
|
||||
@@ -43,6 +54,7 @@ export async function getCalendarGroups(
|
||||
'value',
|
||||
'GET',
|
||||
'/calendarGroups',
|
||||
0,
|
||||
{},
|
||||
);
|
||||
for (const calendar of calendars) {
|
||||
|
||||
@@ -40,33 +40,40 @@ export function getOutlookCredentialType(
|
||||
* credential, app-only Graph has no `/me`, so the node's `mailbox` parameter is
|
||||
* read, validated, and returned as the bare (un-encoded) UPN/ID.
|
||||
*
|
||||
* The mailbox is read at the fixed item index 0 and treated as a per-node constant
|
||||
* for the whole run — the same contract as the `authentication` selector. Encoding
|
||||
* happens once at the URL-build site in `microsoftApiRequest`.
|
||||
* The mailbox accepts expressions and is resolved per item: execute call sites pass
|
||||
* the loop's item index; poll/loadOptions call sites pass a literal 0 — in those
|
||||
* contexts `getNodeParameter`'s 2nd arg is a fallback, not an index. Encoding happens
|
||||
* once at the URL-build site in `microsoftApiRequest`.
|
||||
*/
|
||||
export function resolveMailbox(
|
||||
this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IPollFunctions,
|
||||
credentialType: OutlookCredentialType,
|
||||
itemIndex: number,
|
||||
): string | undefined {
|
||||
if (credentialType !== 'microsoftEntraServicePrincipalApi') return undefined;
|
||||
// Read at item index 0 (per-node constant, same as the `authentication` selector).
|
||||
// loadOptions' getNodeParameter has no itemIndex arg (execute/poll do), so the
|
||||
// { extractValue: true } overload can't be shared across all three contexts.
|
||||
// loadOptions'/poll's getNodeParameter has no itemIndex arg (only execute does), so
|
||||
// the { extractValue: true } overload can't be shared across all three contexts.
|
||||
// Read the raw param and take the id-mode RLC value directly (this RLC has no
|
||||
// extractValue regex, so .value is already the bare mailbox id).
|
||||
const raw = this.getNodeParameter('mailbox', 0);
|
||||
const raw = this.getNodeParameter('mailbox', itemIndex);
|
||||
const value = isResourceLocatorValue(raw) ? raw.value : raw;
|
||||
// A non-string or whitespace-only value collapses to '', which validateMailbox
|
||||
// reports as the "mailbox required" error (not "not valid").
|
||||
// reports as the "mailbox required" error (not "not valid"). A validation error
|
||||
// gets its item index stamped in the router's catch.
|
||||
const mailbox = (typeof value === 'string' ? value : '').trim();
|
||||
validateMailbox(mailbox, this.getNode());
|
||||
return mailbox;
|
||||
}
|
||||
|
||||
// `itemIndex` is REQUIRED (and placed before the defaulted params so call sites
|
||||
// need no positional padding): execute call sites pass the loop index; non-execute
|
||||
// call sites (poll/loadOptions) pass a literal 0, where `getNodeParameter`'s 2nd
|
||||
// arg is a fallback, not an index.
|
||||
export async function microsoftApiRequest(
|
||||
this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IPollFunctions,
|
||||
method: IHttpRequestMethods,
|
||||
resource: string,
|
||||
itemIndex: number,
|
||||
body: IDataObject = {},
|
||||
qs: IDataObject = {},
|
||||
uri?: string,
|
||||
@@ -82,7 +89,7 @@ export async function microsoftApiRequest(
|
||||
: 'https://graph.microsoft.com'
|
||||
).replace(/\/+$/, '');
|
||||
|
||||
const mailbox = resolveMailbox.call(this, credentialType);
|
||||
const mailbox = resolveMailbox.call(this, credentialType, itemIndex);
|
||||
|
||||
let apiUrl: string;
|
||||
if (mailbox !== undefined) {
|
||||
@@ -127,7 +134,7 @@ export async function microsoftApiRequest(
|
||||
let updatedError;
|
||||
// Try to return the error prettier, otherwise return the original one replacing the message with the description
|
||||
try {
|
||||
updatedError = prepareApiError.call(this, error);
|
||||
updatedError = prepareApiError.call(this, error, itemIndex);
|
||||
} catch (e) {}
|
||||
|
||||
if (updatedError) throw updatedError;
|
||||
@@ -145,6 +152,7 @@ export async function microsoftApiRequestAllItems(
|
||||
propertyName: string,
|
||||
method: IHttpRequestMethods,
|
||||
endpoint: string,
|
||||
itemIndex: number,
|
||||
body: IDataObject = {},
|
||||
query: IDataObject = {},
|
||||
headers: IDataObject = {},
|
||||
@@ -160,6 +168,7 @@ export async function microsoftApiRequestAllItems(
|
||||
this,
|
||||
method,
|
||||
endpoint,
|
||||
itemIndex,
|
||||
body,
|
||||
nextLink ? undefined : query, // Do not add query parameters as nextLink already contains them
|
||||
nextLink,
|
||||
@@ -176,6 +185,7 @@ export async function downloadAttachments(
|
||||
this: IExecuteFunctions | IPollFunctions,
|
||||
messages: IDataObject[] | IDataObject,
|
||||
prefix: string,
|
||||
itemIndex: number,
|
||||
) {
|
||||
const elements: INodeExecutionData[] = [];
|
||||
if (!Array.isArray(messages)) {
|
||||
@@ -192,6 +202,7 @@ export async function downloadAttachments(
|
||||
'value',
|
||||
'GET',
|
||||
`/messages/${message.id}/attachments`,
|
||||
itemIndex,
|
||||
{},
|
||||
);
|
||||
for (const [index, attachment] of attachments.entries()) {
|
||||
@@ -199,6 +210,7 @@ export async function downloadAttachments(
|
||||
this,
|
||||
'GET',
|
||||
`/messages/${message.id}/attachments/${attachment.id}/$value`,
|
||||
itemIndex,
|
||||
undefined,
|
||||
{},
|
||||
undefined,
|
||||
@@ -226,12 +238,14 @@ export async function getMimeContent(
|
||||
this: IExecuteFunctions,
|
||||
messageId: string,
|
||||
binaryPropertyName: string,
|
||||
itemIndex: number,
|
||||
outputFileName?: string,
|
||||
) {
|
||||
const response = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/messages/${messageId}/$value`,
|
||||
itemIndex,
|
||||
undefined,
|
||||
{},
|
||||
undefined,
|
||||
@@ -259,6 +273,7 @@ export async function getMimeContent(
|
||||
export async function getSubfolders(
|
||||
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||
folders: IDataObject[],
|
||||
itemIndex: number,
|
||||
addPathToDisplayName = false,
|
||||
) {
|
||||
const returnData: IDataObject[] = [...folders];
|
||||
@@ -269,6 +284,7 @@ export async function getSubfolders(
|
||||
'value',
|
||||
'GET',
|
||||
`/mailFolders/${folder.id}/childFolders`,
|
||||
itemIndex,
|
||||
);
|
||||
|
||||
if (addPathToDisplayName) {
|
||||
@@ -281,7 +297,12 @@ export async function getSubfolders(
|
||||
}
|
||||
|
||||
returnData.push(
|
||||
...(await getSubfolders.call(this, subfolders as IDataObject[], addPathToDisplayName)),
|
||||
...(await getSubfolders.call(
|
||||
this,
|
||||
subfolders as IDataObject[],
|
||||
itemIndex,
|
||||
addPathToDisplayName,
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user