Skip to content

Commit 749a24a

Browse files
fix: stop deployment log polling after cancellation
deploymentStatus terminal-state list was missing CANCELLED, so csdx launch:logs kept polling the deployment-status and deployment-logs queries indefinitely after a deployment was cancelled. Co-Authored-By: Rohan Agrawal <rohan.agrawal@contentstack.com>
1 parent de0bdf5 commit 749a24a

2 files changed

Lines changed: 81 additions & 1 deletion

File tree

src/config/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ const config = {
4242
launchHubUrls: '',
4343
launchBaseUrl: '',
4444
supportedAdapters: ['GitHub'],
45-
deploymentStatus: ['LIVE', 'FAILED', 'SKIPPED', 'DEPLOYED'],
45+
deploymentStatus: ['LIVE', 'FAILED', 'SKIPPED', 'DEPLOYED', 'CANCELLED'],
4646
pollingInterval: 1000,
4747
variablePreparationTypeOptions: [
4848
VariablePreparationTypeOptions.IMPORT_FROM_STACK,

src/util/logs-polling-utilities.test.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,25 @@
1+
import { EventEmitter } from 'events';
12
import { logPolling as cliUtilitiesJestMock } from '../test/mocks/cli-utilities';
3+
import LogPolling from './logs-polling-utilities';
4+
import defaultConfig from '../config';
25

36
type LogPollingCtor = typeof import('./logs-polling-utilities').default;
47

58
jest.mock('@contentstack/cli-utilities', () => cliUtilitiesJestMock);
9+
jest.mock('timers/promises', () => ({ setTimeout: jest.fn().mockResolvedValue(undefined) }));
10+
11+
function makeWatchQuery() {
12+
let subscriber: (result: any) => void = () => {};
13+
return {
14+
subscribe: jest.fn((cb: (result: any) => void) => {
15+
subscriber = cb;
16+
return { unsubscribe: jest.fn() };
17+
}),
18+
setVariables: jest.fn(),
19+
stopPolling: jest.fn(),
20+
emit: (result: any) => subscriber(result),
21+
};
22+
}
623

724
const CONFIG = {
825
deployment: 'd1',
@@ -115,3 +132,66 @@ describe('LogPolling Apollo deprecation regression', () => {
115132
expect(watchQuery).toHaveBeenCalledTimes(1);
116133
});
117134
});
135+
136+
describe('cancelled deployment stops log polling', () => {
137+
function buildInstance(deploymentStatus: string[]) {
138+
const statusWatchQuery = makeWatchQuery();
139+
const logsWatchQuery = makeWatchQuery();
140+
const config = {
141+
deployment: 'd1',
142+
environment: 'e1',
143+
pollingInterval: 1000,
144+
deploymentStatus,
145+
};
146+
const instance = new LogPolling({
147+
apolloManageClient: { watchQuery: jest.fn().mockReturnValue(statusWatchQuery) } as any,
148+
apolloLogsClient: { watchQuery: jest.fn().mockReturnValue(logsWatchQuery) } as any,
149+
config: config as any,
150+
$event: new EventEmitter(),
151+
});
152+
return { instance, statusWatchQuery, logsWatchQuery, config };
153+
}
154+
155+
it('stops status polling once the deployment status is CANCELLED', async () => {
156+
const { instance, statusWatchQuery } = buildInstance(['LIVE', 'FAILED', 'SKIPPED', 'DEPLOYED', 'CANCELLED']);
157+
158+
await instance.deploymentLogs();
159+
statusWatchQuery.emit({ data: { Deployment: { status: 'CANCELLED' } } });
160+
161+
expect(instance.deploymentStatus).toBe('CANCELLED');
162+
expect(statusWatchQuery.stopPolling).toHaveBeenCalledTimes(1);
163+
});
164+
165+
it('stops deployment-logs polling and emits DONE once status is CANCELLED', async () => {
166+
const { instance, statusWatchQuery, logsWatchQuery } = buildInstance([
167+
'LIVE',
168+
'FAILED',
169+
'SKIPPED',
170+
'DEPLOYED',
171+
'CANCELLED',
172+
]);
173+
const events: string[] = [];
174+
(instance as any).$event.on('deployment-logs', (e: any) => events.push(e.message));
175+
176+
await instance.deploymentLogs();
177+
statusWatchQuery.emit({ data: { Deployment: { status: 'CANCELLED' } } });
178+
await logsWatchQuery.emit({ data: { getLogs: [] } });
179+
180+
expect(logsWatchQuery.stopPolling).toHaveBeenCalledTimes(1);
181+
expect(events).toContain('DONE');
182+
});
183+
184+
it('regression guard: keeps polling forever if CANCELLED is missing from deploymentStatus', async () => {
185+
const { instance, statusWatchQuery } = buildInstance(['LIVE', 'FAILED', 'SKIPPED', 'DEPLOYED']);
186+
187+
await instance.deploymentLogs();
188+
statusWatchQuery.emit({ data: { Deployment: { status: 'CANCELLED' } } });
189+
190+
expect(instance.deploymentStatus).toBe('CANCELLED');
191+
expect(statusWatchQuery.stopPolling).not.toHaveBeenCalled();
192+
});
193+
194+
it('real app config (src/config) lists CANCELLED as a terminal deployment status', () => {
195+
expect(defaultConfig.deploymentStatus).toContain('CANCELLED');
196+
});
197+
});

0 commit comments

Comments
 (0)