feat: 支持取消 Jenkins 发布执行

This commit is contained in:
湛兮
2026-06-11 23:22:17 +08:00
parent 588caa4209
commit 6ce1543a0a
4 changed files with 269 additions and 35 deletions
+87 -8
View File
@@ -1,6 +1,7 @@
import { AuditService } from '../audit/audit.service';
import { AppError } from '../common/errors/app-error';
import { DeployNotificationService } from '../notifications/wecom/deploy-notification.service';
import { JenkinsClient } from '../integrations/jenkins/jenkins.client';
import { ProjectSummary } from '../projects/project.types';
import { ProjectsService } from '../projects/projects.service';
import { DeployExecutionService } from './deploy-execution.service';
@@ -66,17 +67,28 @@ describe('DeployRunsService', () => {
operator: input.operator,
trigger: input.trigger ?? 'manual',
})),
updateMemoryStatus: jest.fn((id: string, status: DeployRunStatus) =>
id === baseRun.id ? { ...baseRun, status } : null,
updateRun: jest.fn((id: string, patch: { status?: DeployRunStatus }) =>
runs.some((run) => run.id === id)
? {
...(runs.find((run) => run.id === id) ?? baseRun),
...patch,
}
: null,
),
updateStep: jest.fn((id: string) =>
runs.some((run) => run.id === id)
? {
...(runs.find((run) => run.id === id) ?? baseRun),
status: 'canceled' as const,
}
: null,
),
} satisfies Pick<
DeployRunRepository,
'findMany' | 'findById' | 'create' | 'updateMemoryStatus'
'findMany' | 'findById' | 'create' | 'updateRun' | 'updateStep'
>;
const projects = {
getProject: jest.fn(() =>
Promise.resolve(resolvedProject),
),
getProject: jest.fn(() => Promise.resolve(resolvedProject)),
} satisfies Pick<ProjectsService, 'getProject'>;
const audit = {
record: jest.fn(),
@@ -98,6 +110,26 @@ describe('DeployRunsService', () => {
Promise.resolve({ ...run, status: 'success' as const }),
),
} satisfies Pick<DeployExecutionService, 'execute' | 'syncJenkinsRun'>;
const jenkins = {
healthSummary: jest.fn(() => ({ status: 'ok' as const })),
cancelQueueItem: jest.fn(() =>
Promise.resolve({
target: 'queue' as const,
queueId: 'queue_001',
requested: true as const,
}),
),
stopBuild: jest.fn(() =>
Promise.resolve({
target: 'build' as const,
buildNumber: 7,
requested: true as const,
}),
),
} satisfies Pick<
JenkinsClient,
'healthSummary' | 'cancelQueueItem' | 'stopBuild'
>;
return {
audit,
@@ -111,6 +143,7 @@ describe('DeployRunsService', () => {
audit as unknown as AuditService,
notification as unknown as DeployNotificationService,
execution as unknown as DeployExecutionService,
jenkins as unknown as JenkinsClient,
),
};
}
@@ -262,17 +295,63 @@ describe('DeployRunsService', () => {
expect(execution.execute).toHaveBeenCalled();
});
it('cancels an existing deploy run', async () => {
const { audit, service } = createService();
it('cancels a queued Jenkins execution before marking the run canceled', async () => {
const queuedRun: DeployRunSummary = {
...baseRun,
status: 'queued',
jenkinsQueueId: 'queue_001',
};
const { audit, jenkins, service } = createService({
runs: [queuedRun],
});
const run = await service.cancelRun(baseRun.id);
expect(run.status).toBe('canceled');
expect(jenkins.cancelQueueItem).toHaveBeenCalledWith('queue_001');
expect(jenkins.stopBuild).not.toHaveBeenCalled();
expect(audit.record).toHaveBeenCalledWith(
expect.objectContaining({ action: 'DEPLOY_CANCELED' }),
);
});
it('cancels a running Jenkins build before marking the run canceled', async () => {
const runningRun: DeployRunSummary = {
...baseRun,
status: 'running',
jenkinsBuildNumber: 7,
};
const { jenkins, service } = createService({
runs: [runningRun],
});
const run = await service.cancelRun(baseRun.id);
expect(run.status).toBe('canceled');
expect(jenkins.stopBuild).toHaveBeenCalledWith(
'my-project-dev/test-access-manage-develop',
7,
);
expect(jenkins.cancelQueueItem).not.toHaveBeenCalled();
});
it('rejects already terminal deploy runs before calling Jenkins', async () => {
const canceledRun: DeployRunSummary = {
...baseRun,
status: 'canceled',
jenkinsBuildNumber: 7,
};
const { jenkins, service } = createService({
runs: [canceledRun],
});
await expect(service.cancelRun(baseRun.id)).rejects.toBeInstanceOf(
AppError,
);
expect(jenkins.stopBuild).not.toHaveBeenCalled();
expect(jenkins.cancelQueueItem).not.toHaveBeenCalled();
});
it('retries a deploy run with retry trigger', async () => {
const { repository, service } = createService();