mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
fix(ci): align local verify with PR policy
Fail PR and release quality runs when the impact policy blocks the diff, and accept root-runtime regression coverage across service and utility seams. Tested: bun run check:policy Confidence: high Scope-risk: narrow
This commit is contained in:
parent
435e4ccc9f
commit
8cada04d10
@ -171,6 +171,19 @@ describe('evaluateChangePolicy', () => {
|
||||
expect(covered.missingTestSignals).toEqual([])
|
||||
})
|
||||
|
||||
test('accepts root runtime regression tests across service and utility seams', () => {
|
||||
const result = evaluateChangePolicy(
|
||||
[
|
||||
'src/utils/messages.ts',
|
||||
'src/services/api/streamRetry.test.ts',
|
||||
],
|
||||
['allow-cli-core-change'],
|
||||
)
|
||||
|
||||
expect(result.missingTestSignals).toEqual([])
|
||||
expect(result.blocked).toBe(false)
|
||||
})
|
||||
|
||||
test('blocks coverage baseline and threshold changes without maintainer override', () => {
|
||||
const result = evaluateChangePolicy([
|
||||
'scripts/quality-gate/coverage-baseline.json',
|
||||
|
||||
@ -249,15 +249,9 @@ function missingTestSignals(files: string[]) {
|
||||
const desktopProd = changedProductionFiles(files, (file) => file.startsWith('desktop/src/'))
|
||||
const serverProd = changedProductionFiles(files, (file) => file.startsWith('src/server/'))
|
||||
const adapterProd = changedProductionFiles(files, (file) => file.startsWith('adapters/'))
|
||||
const agentRuntimeProd = changedProductionFiles(files, (file) => (
|
||||
file.startsWith('src/tools/') ||
|
||||
file.startsWith('src/utils/')
|
||||
))
|
||||
const rootRuntimeProd = changedProductionFiles(files, (file) => (
|
||||
file.startsWith('src/') &&
|
||||
!file.startsWith('src/server/') &&
|
||||
!file.startsWith('src/tools/') &&
|
||||
!file.startsWith('src/utils/')
|
||||
!file.startsWith('src/server/')
|
||||
))
|
||||
|
||||
if (desktopProd.length > 0 && !hasMatchingTest(files, (file) => file.startsWith('desktop/src/'))) {
|
||||
@ -269,14 +263,9 @@ function missingTestSignals(files: string[]) {
|
||||
if (adapterProd.length > 0 && !hasMatchingTest(files, (file) => file.startsWith('adapters/'))) {
|
||||
signals.push('Adapter product files changed without an adapter test file in the PR.')
|
||||
}
|
||||
if (agentRuntimeProd.length > 0 && !hasMatchingTest(files, (file) => file.startsWith('src/tools/') || file.startsWith('src/utils/'))) {
|
||||
signals.push('Agent/runtime product files changed without a tools/utils test file in the PR.')
|
||||
}
|
||||
if (rootRuntimeProd.length > 0 && !hasMatchingTest(files, (file) => (
|
||||
file.startsWith('src/') &&
|
||||
!file.startsWith('src/server/') &&
|
||||
!file.startsWith('src/tools/') &&
|
||||
!file.startsWith('src/utils/')
|
||||
!file.startsWith('src/server/')
|
||||
))) {
|
||||
signals.push('Root runtime product files changed without a matching root runtime test file in the PR.')
|
||||
}
|
||||
|
||||
@ -433,6 +433,52 @@ describe('runQualityGate', () => {
|
||||
}
|
||||
})
|
||||
|
||||
test('fails PR mode when the impact report is blocked by policy', async () => {
|
||||
const artifactsDir = mkdtempSync(join(tmpdir(), 'quality-gate-test-'))
|
||||
const lanes: LaneDefinition[] = [
|
||||
{
|
||||
id: 'impact-report',
|
||||
title: 'Impact report',
|
||||
description: 'Writes a blocked policy decision',
|
||||
kind: 'command',
|
||||
command: ['bash', '-lc', [
|
||||
'printf "%s\\n"',
|
||||
'"# PR impact report"',
|
||||
'""',
|
||||
'"Changed files: 1"',
|
||||
'"Areas: cli-core"',
|
||||
'"Labels: none"',
|
||||
'"Blocked: yes"',
|
||||
'"Blocking reasons:"',
|
||||
'"- CLI core changes require maintainer approval."',
|
||||
'""',
|
||||
'"## Required local checks"',
|
||||
'"- bun run check:server"',
|
||||
].join(' ')],
|
||||
requiredForModes: ['pr'],
|
||||
},
|
||||
]
|
||||
|
||||
try {
|
||||
const { report } = await runQualityGateLanes({
|
||||
mode: 'pr',
|
||||
dryRun: false,
|
||||
allowLive: false,
|
||||
baselineTargets: [],
|
||||
rootDir: process.cwd(),
|
||||
artifactsDir,
|
||||
runId: 'blocked-impact-test',
|
||||
}, lanes)
|
||||
|
||||
expect(report.impact?.blocked).toBe(true)
|
||||
expect(report.results[0].status).toBe('failed')
|
||||
expect(report.results[0].error).toContain('change policy blocked')
|
||||
expect(report.summary.failed).toBe(1)
|
||||
} finally {
|
||||
rmSync(artifactsDir, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
test('release mode treats skipped live lanes as failures', async () => {
|
||||
const artifactsDir = mkdtempSync(join(tmpdir(), 'quality-gate-test-'))
|
||||
const lanes: LaneDefinition[] = [
|
||||
|
||||
@ -432,6 +432,33 @@ function enforceReleaseLiveLanes(
|
||||
})
|
||||
}
|
||||
|
||||
function enforceBlockedImpact(
|
||||
options: QualityGateOptions,
|
||||
results: LaneResult[],
|
||||
) {
|
||||
if (options.dryRun || (options.mode !== 'pr' && options.mode !== 'release')) {
|
||||
return results
|
||||
}
|
||||
|
||||
const impact = parseImpactSummary(results)
|
||||
if (!impact?.blocked) {
|
||||
return results
|
||||
}
|
||||
|
||||
return results.map((result) => {
|
||||
if (result.id !== 'impact-report' || result.status === 'failed') {
|
||||
return result
|
||||
}
|
||||
|
||||
return {
|
||||
...result,
|
||||
status: 'failed' as const,
|
||||
exitCode: result.exitCode === 0 ? 1 : result.exitCode,
|
||||
error: 'Local change policy blocked this diff; inspect the impact report and use maintainer overrides only when explicitly approved.',
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function runQualityGate(options: QualityGateOptions) {
|
||||
return runQualityGateLanes(options, lanesForMode(options.mode, options.baselineTargets))
|
||||
}
|
||||
@ -454,7 +481,8 @@ export async function runQualityGateLanes(
|
||||
const result = await executeLane(lane, runOptions)
|
||||
rawResults.push(withLaneMetadata(lane, result))
|
||||
}
|
||||
const results = enforceReleaseLiveLanes(options, selectedLanes, rawResults)
|
||||
const releaseResults = enforceReleaseLiveLanes(options, selectedLanes, rawResults)
|
||||
const results = enforceBlockedImpact(options, releaseResults)
|
||||
|
||||
const report: QualityGateReport = {
|
||||
schemaVersion: 1,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user