diff --git a/src/commands/launch/rollback.test.ts b/src/commands/launch/rollback.test.ts index ad5cf33..721f342 100644 --- a/src/commands/launch/rollback.test.ts +++ b/src/commands/launch/rollback.test.ts @@ -82,23 +82,73 @@ describe('Rollback Command', () => { jest.clearAllMocks(); }); - it('exits when no rollback-eligible deployments are available', async () => { - const noEligibleResponse = { - data: { - Environments: { - edges: [ - { - node: { - uid: 'env-uid', - name: 'Default', - deployments: { edges: [{ node: liveDeployment }] }, - }, + const noEligibleResponse = { + data: { + Environments: { + edges: [ + { + node: { + uid: 'env-uid', + name: 'Default', + deployments: { edges: [{ node: liveDeployment }] }, }, - ], - }, + }, + ], }, - }; - const query = jest.fn().mockResolvedValueOnce(noEligibleResponse); + }, + }; + + it('exits when no rollback-eligible deployments are available', async () => { + const query = jest + .fn() + .mockResolvedValueOnce(noEligibleResponse) + .mockResolvedValueOnce({ + data: { RollbackSettings: { isEnabled: true, retentionCount: 3 } }, + }); + const mutate = jest.fn(); + const cmd = buildCommand({ environment: 'Default' }, query, mutate); + jest + .spyOn(cmd as any, 'fetchCurrentLiveDeployment') + .mockResolvedValueOnce(liveDeployment); + + await expect((cmd as any).rollbackDeployment()).rejects.toThrow('process.exit:1'); + + expect(mutate).not.toHaveBeenCalled(); + expect(exitMock).toHaveBeenCalledWith(1); + expect((cmd as any).log).toHaveBeenCalledWith( + 'No rollback-eligible deployments are available for this environment.', + 'error', + ); + }); + + it('shows a plan-not-available message when instant rollback is not entitled', async () => { + const query = jest + .fn() + .mockResolvedValueOnce(noEligibleResponse) + .mockResolvedValueOnce({ + data: { RollbackSettings: { isEnabled: false, retentionCount: 0 } }, + }); + const mutate = jest.fn(); + const cmd = buildCommand({ environment: 'Default' }, query, mutate); + jest + .spyOn(cmd as any, 'fetchCurrentLiveDeployment') + .mockResolvedValueOnce(liveDeployment); + + await expect((cmd as any).rollbackDeployment()).rejects.toThrow('process.exit:1'); + + expect(mutate).not.toHaveBeenCalled(); + expect(exitMock).toHaveBeenCalledWith(1); + expect((cmd as any).log).toHaveBeenCalledWith( + 'Instant rollback isn\'t available on your organization\'s plan.', + 'error', + ); + }); + + it('falls back to the generic message when the settings fetch fails', async () => { + const query = jest + .fn() + .mockResolvedValueOnce(noEligibleResponse) + .mockRejectedValueOnce(new Error('network error')); const mutate = jest.fn(); const cmd = buildCommand({ environment: 'Default' }, query, mutate); jest diff --git a/src/commands/launch/rollback.ts b/src/commands/launch/rollback.ts index 8cf536a..84b3ddb 100644 --- a/src/commands/launch/rollback.ts +++ b/src/commands/launch/rollback.ts @@ -10,6 +10,7 @@ import { environmentsQuery, latestLiveDeploymentQuery, rollbackDeploymentMutation, + rollbackSettingsQuery, } from '../../graphql'; import { Logger, selectOrg, selectProject } from '../../util'; @@ -88,7 +89,12 @@ export default class Rollback extends BaseCommand { const eligibleSorted = this.getEligibleSortedDeployments(environment, currentLive?.uid); if (isEmpty(eligibleSorted)) { - this.log('No rollback-eligible deployments are available for this environment.', 'error'); + const settings = await this.fetchRollbackSettings(); + if (settings?.isEnabled === false) { + this.log('Instant rollback isn\'t available on your organization\'s plan.', 'error'); + } else { + this.log('No rollback-eligible deployments are available for this environment.', 'error'); + } process.exit(1); } @@ -194,6 +200,18 @@ export default class Rollback extends BaseCommand { .catch(() => undefined); } + /** + * @method fetchRollbackSettings - org-level entitlement; undefined on any fetch failure + * + * @memberof Rollback + */ + async fetchRollbackSettings(): Promise<{ isEnabled: boolean; retentionCount: number } | undefined> { + return this.apolloClient + .query({ query: rollbackSettingsQuery }) + .then(({ data }) => data?.RollbackSettings) + .catch(() => undefined); + } + /** * @method getEligibleSortedDeployments - eligible deployments excluding current live, sorted by number desc * diff --git a/src/graphql/queries.ts b/src/graphql/queries.ts index 1cc1578..e3d9886 100755 --- a/src/graphql/queries.ts +++ b/src/graphql/queries.ts @@ -185,6 +185,15 @@ const latestLiveDeploymentQuery: DocumentNode = gql` } `; +const rollbackSettingsQuery: DocumentNode = gql` + query RollbackSettings { + RollbackSettings { + isEnabled + retentionCount + } + } +`; + const environmentsQuery: DocumentNode = gql` query Environments($skipRollbackData: Boolean = true) { Environments { @@ -228,4 +237,5 @@ export { serverlessLogsQuery, latestLiveDeploymentQuery, environmentsQuery, + rollbackSettingsQuery, };