fix(Xero Node): Send tenant header on every page when returning all items (#39431)

This commit is contained in:
Elias Meire 2026-09-25 06:56:53 +00:00 committed by GitHub
parent a3aa6c0074
commit 58e55b5898
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 55 additions and 9 deletions

View File

@ -13,7 +13,7 @@ export async function xeroApiRequest(
method: IHttpRequestMethods,
resource: string,
body: any = {},
{ organizationId, ...body }: any = {},
qs: IDataObject = {},
uri?: string,
headers: IDataObject = {},
@ -21,6 +21,7 @@ export async function xeroApiRequest(
const options: IRequestOptions = {
headers: {
'Content-Type': 'application/json',
...(organizationId && { 'Xero-tenant-id': organizationId }),
},
method,
body,
@ -29,10 +30,6 @@ export async function xeroApiRequest(
json: true,
};
try {
if (body.organizationId) {
options.headers = { ...options.headers, 'Xero-tenant-id': body.organizationId };
delete body.organizationId;
}
if (Object.keys(headers).length !== 0) {
options.headers = Object.assign({}, options.headers, headers);
}
@ -57,11 +54,11 @@ export async function xeroApiRequestAllItems(
const returnData: IDataObject[] = [];
let responseData;
query.page = 1;
let page = 1;
do {
responseData = await xeroApiRequest.call(this, method, endpoint, body, query);
query.page++;
responseData = await xeroApiRequest.call(this, method, endpoint, body, { ...query, page });
page++;
returnData.push.apply(returnData, responseData[propertyName] as IDataObject[]);
} while (responseData[propertyName].length !== 0);

View File

@ -1,5 +1,11 @@
import { mock, mockDeep } from 'vitest-mock-extended';
import type { ILoadOptionsFunctions, INode } from 'n8n-workflow';
import type {
IDataObject,
IExecuteFunctions,
ILoadOptionsFunctions,
INode,
NodeExecutionWithMetadata,
} from 'n8n-workflow';
import { Xero } from '../Xero.node';
@ -76,4 +82,47 @@ describe('Xero Node', () => {
});
});
});
describe.each([
['invoice', 'Invoices'],
['contact', 'Contacts'],
])('%s getAll with returnAll', (resource, propertyName) => {
it('should send the Xero-tenant-id header on every page', async () => {
const xero = new Xero();
const executeFunctions = mockDeep<IExecuteFunctions>();
const params: IDataObject = {
resource,
operation: 'getAll',
organizationId: 'test-org-id',
returnAll: true,
options: {},
};
executeFunctions.getInputData.mockReturnValue([{ json: {} }]);
executeFunctions.getNode.mockReturnValue(mock<INode>());
executeFunctions.getNodeParameter.mockImplementation((name: string) => params[name]);
executeFunctions.helpers.constructExecutionMetaData.mockImplementation(
(data) => data as NodeExecutionWithMetadata[],
);
executeFunctions.helpers.returnJsonArray.mockImplementation((data) =>
(Array.isArray(data) ? data : [data]).map((json) => ({ json })),
);
executeFunctions.helpers.requestOAuth2
.mockResolvedValueOnce({ [propertyName]: [{ id: 1 }] })
.mockResolvedValueOnce({ [propertyName]: [{ id: 2 }] })
.mockResolvedValueOnce({ [propertyName]: [] });
const result = await xero.execute.call(executeFunctions);
expect(result[0].map(({ json }) => json)).toEqual([{ id: 1 }, { id: 2 }]);
const calls = executeFunctions.helpers.requestOAuth2.mock.calls;
expect(calls).toHaveLength(3);
calls.forEach(([, options], index) => {
expect(options.headers).toEqual(
expect.objectContaining({ 'Xero-tenant-id': 'test-org-id' }),
);
expect(options.qs).toEqual(expect.objectContaining({ page: index + 1 }));
});
});
});
});