From adad8882995597dcf33f06785730113a747c2fd6 Mon Sep 17 00:00:00 2001 From: Ryan Duguid <152749594+ryanduguid@users.noreply.github.com> Date: Thu, 13 Aug 2026 02:09:17 +1000 Subject: [PATCH 1/2] fix: wire Payroll AU v2 API client --- src/XeroClient.ts | 29 +++++++++++++++++++---------- src/gen/api/apis.ts | 4 +++- src/test/xeroClient.spec.ts | 7 +++++++ 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/XeroClient.ts b/src/XeroClient.ts index 100e18173..a90f5c4c4 100644 --- a/src/XeroClient.ts +++ b/src/XeroClient.ts @@ -59,11 +59,25 @@ export class XeroClient { this.filesApi = new xero.FilesApi(); this.projectApi = new xero.ProjectApi(); this.payrollAUApi = new xero.PayrollAuApi(); + this.payrollAUV2Api = new xero.PayrollAuV2Api(); this.bankFeedsApi = new xero.BankFeedsApi(); this.payrollUKApi = new xero.PayrollUkApi(); this.payrollNZApi = new xero.PayrollNzApi(); this.appStoreApi = new xero.AppStoreApi(); this.financeApi = new xero.FinanceApi(); + this.apiClients = [ + this.accountingApi, + this.assetApi, + this.filesApi, + this.projectApi, + this.payrollAUApi, + this.payrollAUV2Api, + this.bankFeedsApi, + this.payrollUKApi, + this.payrollNZApi, + this.appStoreApi, + this.financeApi, + ]; }; private _tokenSet: TokenSet = new TokenSet; @@ -74,11 +88,13 @@ export class XeroClient { readonly filesApi: xero.FilesApi; readonly projectApi: xero.ProjectApi; readonly payrollAUApi: xero.PayrollAuApi; + readonly payrollAUV2Api: xero.PayrollAuV2Api; readonly bankFeedsApi: xero.BankFeedsApi; readonly payrollUKApi: xero.PayrollUkApi; readonly payrollNZApi: xero.PayrollNzApi; readonly appStoreApi: xero.AppStoreApi; readonly financeApi: xero.FinanceApi; + private readonly apiClients: Array<{ accessToken: string }>; openIdClient: Client; // from openid-client @@ -273,15 +289,8 @@ export class XeroClient { throw new Error('Access token is undefined!'); } - this.accountingApi.accessToken = accessToken; - this.assetApi.accessToken = accessToken; - this.filesApi.accessToken = accessToken; - this.projectApi.accessToken = accessToken; - this.payrollAUApi.accessToken = accessToken; - this.bankFeedsApi.accessToken = accessToken; - this.payrollUKApi.accessToken = accessToken; - this.payrollNZApi.accessToken = accessToken; - this.appStoreApi.accessToken = accessToken; - this.financeApi.accessToken = accessToken; + this.apiClients.forEach(apiClient => { + apiClient.accessToken = accessToken; + }); } } diff --git a/src/gen/api/apis.ts b/src/gen/api/apis.ts index 459e0f40b..6704aeb8c 100644 --- a/src/gen/api/apis.ts +++ b/src/gen/api/apis.ts @@ -3,6 +3,7 @@ export * from './assetApi'; export * from './projectApi'; export * from './filesApi'; export * from './payrollAUApi'; +export * from './payrollAUV2Api'; export * from './bankfeedsApi'; export * from './payrollUKApi'; export * from './payrollNZApi'; @@ -13,9 +14,10 @@ import { AssetApi } from './assetApi'; import { FilesApi } from './filesApi'; import { ProjectApi } from './projectApi'; import { PayrollAuApi } from './payrollAUApi'; +import { PayrollAuV2Api } from './payrollAUV2Api'; import { BankFeedsApi } from './bankfeedsApi'; import { PayrollUkApi } from './payrollUKApi'; import { PayrollNzApi } from './payrollNZApi'; import { AppStoreApi } from './appStoreApi'; import { FinanceApi } from './financeApi'; -export const APIS = [AccountingApi, AssetApi, FilesApi, ProjectApi, PayrollAuApi, BankFeedsApi, PayrollUkApi, PayrollNzApi, AppStoreApi, FinanceApi]; \ No newline at end of file +export const APIS = [AccountingApi, AssetApi, FilesApi, ProjectApi, PayrollAuApi, PayrollAuV2Api, BankFeedsApi, PayrollUkApi, PayrollNzApi, AppStoreApi, FinanceApi]; diff --git a/src/test/xeroClient.spec.ts b/src/test/xeroClient.spec.ts index 89a8cb4ac..6ef252393 100644 --- a/src/test/xeroClient.spec.ts +++ b/src/test/xeroClient.spec.ts @@ -110,11 +110,18 @@ describe('the XeroClient', () => { expect(xeroClient).toHaveProperty('bankFeedsApi') expect(xeroClient).toHaveProperty('projectApi') expect(xeroClient).toHaveProperty('payrollAUApi') + expect(xeroClient).toHaveProperty('payrollAUV2Api') expect(xeroClient).toHaveProperty('payrollUKApi') expect(xeroClient).toHaveProperty('payrollNZApi') expect(xeroClient).toHaveProperty('appStoreApi') }); + it('propagates access tokens to Payroll AU v2', () => { + xero.setTokenSet(tokenSet) + + expect((xero.payrollAUV2Api as any).authentications.OAuth2.accessToken).toEqual(tokenSet.access_token) + }); + it('readTokenSet() returns the tokenSet', async () => { const xeroTokenSet = await xero.readTokenSet() expect(xeroTokenSet).toEqual(tokenSet) From 8f464d6915a42fb8e413747788e1e3cd89455cf9 Mon Sep 17 00:00:00 2001 From: Ryan Duguid <152749594+ryanduguid@users.noreply.github.com> Date: Wed, 19 Aug 2026 15:09:21 +1000 Subject: [PATCH 2/2] refactor: derive the API client list instead of hand-maintaining it apiClients was a fourth parallel list, alongside the constructor assignments, the field declarations and the generated APIS export, with nothing keeping them in step. A client added to the constructor but missed here was silently skipped by setAccessToken(), leaving every call on it returning 401. The list is now derived from the clients built in the constructor, filtered against xero.APIS, so wiring a client is enough to have it receive the token. Adds a test that walks xero.APIS and asserts each generated client actually holds the access token. It fails on divergence: dropping financeApi from the old hand-maintained array leaves its accessToken empty while every other test still passes. --- src/XeroClient.ts | 23 +++++++++-------------- src/test/xeroClient.spec.ts | 12 ++++++++++++ 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/XeroClient.ts b/src/XeroClient.ts index a90f5c4c4..f938bf370 100644 --- a/src/XeroClient.ts +++ b/src/XeroClient.ts @@ -65,19 +65,6 @@ export class XeroClient { this.payrollNZApi = new xero.PayrollNzApi(); this.appStoreApi = new xero.AppStoreApi(); this.financeApi = new xero.FinanceApi(); - this.apiClients = [ - this.accountingApi, - this.assetApi, - this.filesApi, - this.projectApi, - this.payrollAUApi, - this.payrollAUV2Api, - this.bankFeedsApi, - this.payrollUKApi, - this.payrollNZApi, - this.appStoreApi, - this.financeApi, - ]; }; private _tokenSet: TokenSet = new TokenSet; @@ -94,7 +81,6 @@ export class XeroClient { readonly payrollNZApi: xero.PayrollNzApi; readonly appStoreApi: xero.AppStoreApi; readonly financeApi: xero.FinanceApi; - private readonly apiClients: Array<{ accessToken: string }>; openIdClient: Client; // from openid-client @@ -283,6 +269,15 @@ export class XeroClient { }); } + // Derived from the clients built in the constructor and cross-checked against + // xero.APIS, the generated list of every API client class, so a newly wired + // client cannot be missed here. + private get apiClients(): Array<{ accessToken: string }> { + return Object.values(this).filter( + (value): value is { accessToken: string } => xero.APIS.some(apiClass => value instanceof apiClass) + ); + } + private setAccessToken(): void { const accessToken = this._tokenSet.access_token; if (typeof accessToken === 'undefined') { diff --git a/src/test/xeroClient.spec.ts b/src/test/xeroClient.spec.ts index 6ef252393..c09040973 100644 --- a/src/test/xeroClient.spec.ts +++ b/src/test/xeroClient.spec.ts @@ -1,4 +1,5 @@ import { XeroClient } from "../XeroClient"; +import * as xeroApis from "../gen/api"; const tokenSetJson = require("./mocks/tokenSet.json"); const refreshedTokenSetJson = require("./mocks/refreshedTokenSet.json"); const connectionsResponse = require("./mocks/connectionsResponse.json"); @@ -122,6 +123,17 @@ describe('the XeroClient', () => { expect((xero.payrollAUV2Api as any).authentications.OAuth2.accessToken).toEqual(tokenSet.access_token) }); + it('propagates access tokens to every generated API client', () => { + xero.setTokenSet(tokenSet) + + xeroApis.APIS.forEach(apiClass => { + const wiredClient: any = Object.values(xero).find(value => value instanceof apiClass) + + expect(wiredClient).toBeDefined() + expect(wiredClient.authentications.OAuth2.accessToken).toEqual(tokenSet.access_token) + }) + }); + it('readTokenSet() returns the tokenSet', async () => { const xeroTokenSet = await xero.readTokenSet() expect(xeroTokenSet).toEqual(tokenSet)