From d48b09bb176dd1ee8b6d12970c14daa530e0d386 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ales=CC=8C=20Remta?= <34923415+packocz@users.noreply.github.com> Date: Mon, 30 Jun 2025 15:41:28 +0200 Subject: [PATCH] feat(permsets): add maxwaittime ENV variable PSG Awaiter --- .../PermissionSetGroupUpdateAwaiter.ts | 40 ++++- .../PermissionSetGroupUpdateAwaiter.test.ts | 156 +++++++++++++++++- 2 files changed, 185 insertions(+), 11 deletions(-) diff --git a/src/core/permsets/PermissionSetGroupUpdateAwaiter.ts b/src/core/permsets/PermissionSetGroupUpdateAwaiter.ts index ad8d2f716..f320d714f 100644 --- a/src/core/permsets/PermissionSetGroupUpdateAwaiter.ts +++ b/src/core/permsets/PermissionSetGroupUpdateAwaiter.ts @@ -3,17 +3,31 @@ import SFPLogger, { Logger, LoggerLevel } from '@flxbl-io/sfp-logger'; import QueryHelper from '../queryHelper/QueryHelper'; import { delay } from '../utils/Delay'; -const psGroupQuery = `SELECT Id,MasterLabel,Status FROM PermissionSetGroup WHERE Status IN ('Updating', 'Outdated')`; +const psGroupQuery = `SELECT Id,MasterLabel,Status FROM PermissionSetGroup WHERE Status = 'Updating'`; export default class PermissionSetGroupUpdateAwaiter { - constructor(private connection: Connection, private logger: Logger, private intervalBetweenRepeats = 60000) {} + constructor( + private connection: Connection, + private logger: Logger, + private intervalBetweenRepeats = 30000 + ) {} + + private getMaxWaitingTimeMilliseconds(): number { + const maxWaitingTimeInMinutes = process.env.PSG_AWAITER_TIMEOUT_MINUTES ?? undefined; + if (maxWaitingTimeInMinutes) { + const maxWaitingTimeInMinutesParsed = Number(maxWaitingTimeInMinutes); + if (isNaN(maxWaitingTimeInMinutesParsed) || maxWaitingTimeInMinutesParsed <= 0) { + SFPLogger.log(`PSG_AWAITER_TIMEOUT_MINUTES env variable must be a positive number [${maxWaitingTimeInMinutes}]`, LoggerLevel.ERROR, this.logger); + } + return maxWaitingTimeInMinutesParsed * 60 * 1000; // Convert minutes to milliseconds + } + } async waitTillAllPermissionSetGroupIsUpdated() { - SFPLogger.log( - `Checking status of permission sets group..`, - LoggerLevel.INFO, - this.logger - ); + const maxWaitingTime = this.getMaxWaitingTimeMilliseconds(); + + SFPLogger.log(`Checking status of permission sets group..`, LoggerLevel.INFO, this.logger); + let totalTimeWaited = 0; while (true) { try { let records = await QueryHelper.query(psGroupQuery, this.connection, false); @@ -29,6 +43,17 @@ export default class PermissionSetGroupUpdateAwaiter { this.logger ); await delay(this.intervalBetweenRepeats); + totalTimeWaited += this.intervalBetweenRepeats; + if (maxWaitingTime && (totalTimeWaited > maxWaitingTime)) { + SFPLogger.log( + `Max waiting time of ${ + maxWaitingTime / 1000 + } seconds exceeded. Proceeding with deployment`, + LoggerLevel.WARN, + this.logger + ); + break; + } } else { SFPLogger.log( `Proceeding with deployment, as no PermissionSetGroups are being updated`, @@ -44,3 +69,4 @@ export default class PermissionSetGroupUpdateAwaiter { } } } + \ No newline at end of file diff --git a/tests/core/permsets/PermissionSetGroupUpdateAwaiter.test.ts b/tests/core/permsets/PermissionSetGroupUpdateAwaiter.test.ts index 30a3f904e..ed80c3fe9 100644 --- a/tests/core/permsets/PermissionSetGroupUpdateAwaiter.test.ts +++ b/tests/core/permsets/PermissionSetGroupUpdateAwaiter.test.ts @@ -1,11 +1,40 @@ import { MockTestOrgData, TestContext } from '../../../node_modules/@salesforce/core/lib/testSetup'; import { AuthInfo, Connection, OrgConfigProperties } from '@salesforce/core'; +import { ConsoleLogger } from '@flxbl-io/sfp-logger'; import { AnyJson } from '@salesforce/ts-types'; const $$ = new TestContext(); import PermissionSetGroupUpdateAwaiter from '../../../src/core/permsets/PermissionSetGroupUpdateAwaiter'; import { expect } from '@jest/globals'; describe('Await till permissionsets groups are updated', () => { + const noUpdatingPsgRecords: AnyJson = { + records: [], + }; + const someUpdatingPsgRecords: AnyJson = { + records: [ + { + attributes: { + type: 'PermissionSetGroup', + url: '/services/data/v64.0/sobjects/PermissionSetGroup/0PG250000008nhNGAQ', + }, + Id: '0PG250000008nhNGAQ', + MasterLabel: 'PSG1', + Status: 'Updating', + }, + { + attributes: { + type: 'PermissionSetGroup', + url: '/services/data/v64.0/sobjects/PermissionSetGroup/0PG250000008nnVGAQ', + }, + Id: '0PG250000008nnVGAQ', + MasterLabel: 'PSG2', + Status: 'Updating', + }, + ], + }; + + jest.spyOn(require('../../../src/core/utils/Delay'), 'delay').mockImplementation(() => Promise.resolve()); + it('should return if all permsets groups are updated', async () => { const testData = new MockTestOrgData(); @@ -15,21 +44,140 @@ describe('Await till permissionsets groups are updated', () => { contents: await testData.getConfig(), }); - let records: AnyJson = { - records: [], + $$.fakeConnectionRequest = (request: AnyJson): Promise => { + return Promise.resolve(noUpdatingPsgRecords); + }; + + const connection: Connection = await Connection.create({ + authInfo: await AuthInfo.create({ username: testData.username }), + }); + + let permissionSetGroupUpdateAwaiter: PermissionSetGroupUpdateAwaiter = new PermissionSetGroupUpdateAwaiter( + connection, new ConsoleLogger() + ); + await expect(permissionSetGroupUpdateAwaiter.waitTillAllPermissionSetGroupIsUpdated()).resolves.toBeUndefined(); + }); + + it('should return if all permsets groups are updated after waiting for some time', async () => { + const testData = new MockTestOrgData(); + + await $$.stubConfig({ [OrgConfigProperties.TARGET_ORG]: testData.username }); + await $$.stubAuths(testData); + $$.setConfigStubContents('AuthInfoConfig', { + contents: await testData.getConfig(), + }); + + let tryCount = 0; + $$.fakeConnectionRequest = (request: AnyJson): Promise => { + if (tryCount === 0) { + tryCount++; + return Promise.resolve(someUpdatingPsgRecords); + } + return Promise.resolve(noUpdatingPsgRecords); + }; + + const connection: Connection = await Connection.create({ + authInfo: await AuthInfo.create({ username: testData.username }), + }); + + let permissionSetGroupUpdateAwaiter: PermissionSetGroupUpdateAwaiter = new PermissionSetGroupUpdateAwaiter( + connection, + new ConsoleLogger() + ); + await expect(permissionSetGroupUpdateAwaiter.waitTillAllPermissionSetGroupIsUpdated()).resolves.toBeUndefined(); + }); + + it('should keep trying until all permsets groups are updated if there is no maximum wait time', async () => { + const testData = new MockTestOrgData(); + + await $$.stubConfig({ [OrgConfigProperties.TARGET_ORG]: testData.username }); + await $$.stubAuths(testData); + $$.setConfigStubContents('AuthInfoConfig', { + contents: await testData.getConfig(), + }); + + let tryCount = 0; + $$.fakeConnectionRequest = (request: AnyJson): Promise => { + if (tryCount < 5) { + // 5th try + tryCount++; + return Promise.resolve(someUpdatingPsgRecords); + } + return Promise.resolve(noUpdatingPsgRecords); }; + + const connection: Connection = await Connection.create({ + authInfo: await AuthInfo.create({ username: testData.username }), + }); + + let permissionSetGroupUpdateAwaiter: PermissionSetGroupUpdateAwaiter = new PermissionSetGroupUpdateAwaiter( + connection, + new ConsoleLogger() + ); + await expect(permissionSetGroupUpdateAwaiter.waitTillAllPermissionSetGroupIsUpdated()).resolves.toBeUndefined(); + expect(tryCount).toBe(5); + }); + + it('should keep trying until until max wait time is reached', async () => { + const testData = new MockTestOrgData(); + + await $$.stubConfig({ [OrgConfigProperties.TARGET_ORG]: testData.username }); + await $$.stubAuths(testData); + $$.setConfigStubContents('AuthInfoConfig', { + contents: await testData.getConfig(), + }); + $$.fakeConnectionRequest = (request: AnyJson): Promise => { - return Promise.resolve(records); + return Promise.resolve(someUpdatingPsgRecords); }; const connection: Connection = await Connection.create({ authInfo: await AuthInfo.create({ username: testData.username }), }); + process.env.PSG_AWAITER_TIMEOUT_MINUTES = '0.25'; // 15 seconds let permissionSetGroupUpdateAwaiter: PermissionSetGroupUpdateAwaiter = new PermissionSetGroupUpdateAwaiter( connection, - null + new ConsoleLogger(), + 100, // try every 100ms ); await expect(permissionSetGroupUpdateAwaiter.waitTillAllPermissionSetGroupIsUpdated()).resolves.toBeUndefined(); }); + + it('should not reach maximum time if all permsets groups udpated', async () => { + const testData = new MockTestOrgData(); + + await $$.stubConfig({ [OrgConfigProperties.TARGET_ORG]: testData.username }); + await $$.stubAuths(testData); + $$.setConfigStubContents('AuthInfoConfig', { + contents: await testData.getConfig(), + }); + + let tryCount = 0; + $$.fakeConnectionRequest = (request: AnyJson): Promise => { + if (tryCount < 3) { + // 3rd try in 300ms + tryCount++; + return Promise.resolve(someUpdatingPsgRecords); + } + return Promise.resolve(noUpdatingPsgRecords); + }; + + + + const connection: Connection = await Connection.create({ + authInfo: await AuthInfo.create({ username: testData.username }), + }); + + process.env.PSG_AWAITER_TIMEOUT_MINUTES = '0.5'; // 30 seconds + let permissionSetGroupUpdateAwaiter: PermissionSetGroupUpdateAwaiter = new PermissionSetGroupUpdateAwaiter( + connection, + new ConsoleLogger(), + 100, // try every 100ms + ); + await expect(permissionSetGroupUpdateAwaiter.waitTillAllPermissionSetGroupIsUpdated()).resolves.toBeUndefined(); + expect(tryCount).toBe(3); // 4 tries in total, first and then waiting 3 times + }); }); + + \ No newline at end of file