import assert from 'node:assert/strict' import { readFile } from 'node:fs/promises' import test from 'node:test' const pipeline = (await readFile(new URL('../Jenkinsfile', import.meta.url), 'utf8')) .replaceAll('\r\n', '\n') const servicePipeline = (await readFile(new URL('../Jenkinsfile.service', import.meta.url), 'utf8')) .replaceAll('\r\n', '\n') test('coordinator releases its builder while waiting for downstream jobs', () => { assert.match(pipeline, /pipeline \{[\s\S]*?agent none/) assert.match(pipeline, /stage\('确定构建范围'\) \{[\s\S]*?agent \{ label 'java-builder' \}/) assert.match(pipeline, /agent \{ label 'java-builder' \}/) assert.doesNotMatch(pipeline, /agent any/) assert.doesNotMatch(pipeline, /cleanWs\(/) assert.doesNotMatch(pipeline, /^\s*(?:&&|\|\|)/m) }) test('coordinator runs selected tests once before parallel service packaging', () => { const gateIndex = pipeline.indexOf("stage('统一 Maven 测试门禁')") const dispatchIndex = pipeline.indexOf("stage('并行调度独立服务 Job')") const staleReportCleanupIndex = pipeline.indexOf( "find . -type f -path '*/target/surefire-reports/TEST-*.xml' -delete" ) const selectedTestIndex = pipeline.indexOf('-pl ${selectedModules} -am test') assert.ok(gateIndex >= 0, 'unified test gate must exist') assert.ok(dispatchIndex > gateIndex, 'unified tests must finish before downstream dispatch') assert.ok(staleReportCleanupIndex > gateIndex, 'stale Surefire reports must be removed inside the test gate') assert.ok(selectedTestIndex > staleReportCleanupIndex, 'stale reports must be removed before selected tests run') assert.match(pipeline, /-pl \$\{selectedModules\} -am test/) assert.match(pipeline, /junit allowEmptyResults: true, testResults: '\*\*\/target\/surefire-reports\/TEST-\*\.xml'/) assert.match(pipeline, /booleanParam\(name: 'SKIP_TESTS', value: true\)/) assert.doesNotMatch(pipeline, /TESTED_COMMIT/) }) test('AUTO detects database migrations without expanding them to all services', () => { assert.match(pipeline, /def databaseMigrationPaths = \['deploy\/sql\/'\]/) assert.match(pipeline, /List serviceRelevantChanges = changed\.findAll/) assert.match(pipeline, /!databaseMigrationPaths\.any \{ prefix -> path\.startsWith\(prefix\) \}/) assert.match(pipeline, /env\.DATABASE_CHANGE_DETECTED = databaseChanged \? 'true' : 'false'/) }) test('database-only changes are reported as a separate migration action', () => { assert.match(pipeline, /仅数据库迁移变更 \| 需单独审核执行/) assert.match(pipeline, /统一 SQL 须独立审核并执行/) }) test('coordinator cannot initialize test databases', () => { assert.doesNotMatch(pipeline, /INITIALIZE_TEST_DATABASE/) assert.doesNotMatch(pipeline, /initialize-test-databases\.sh/) assert.doesNotMatch(pipeline, /测试数据库初始化/) }) test('coordinator defaults event bridge deployments to both non-production regions', () => { assert.match(pipeline, /choice\(name: 'EVENT_BRIDGE_REGION', choices: \['BOTH', 'CN', 'OVERSEAS'\]/) assert.match(pipeline, /\['BOTH', 'SELECT', 'CN', 'OVERSEAS'\]\.contains\(bridgeRegion\)/) assert.match(pipeline, /兼容旧 Jenkins 参数 SELECT:本次按 BOTH/) assert.match(pipeline, /事件桥 BOTH 仅支持开发或测试环境/) assert.match(pipeline, /String downstreamImageTag = deployBothBridges[\s\S]*?\? "\$\{env\.IMAGE_TAG\}-both"/) assert.match(pipeline, /String downstreamBridgeRegion = deployBothBridges[\s\S]*?\? 'CN'/) assert.match(pipeline, /service == 'event-bridge-service' \? params\.EVENT_BRIDGE_REGION : 'CN'/) assert.match(pipeline, /string\(name: 'EVENT_BRIDGE_REGION', value: downstreamBridgeRegion\)/) }) test('production deploy uses DEPLOY as the single explicit release switch', () => { assert.match(pipeline, /booleanParam\(name: 'DEPLOY'/) assert.match(pipeline, /test "\$\(git rev-parse HEAD\)" = "\$\(git rev-parse origin\/main\)"/) assert.match(pipeline, /env\.RESOLVED_ENVIRONMENT == 'production' && params\.SKIP_TESTS/) assert.doesNotMatch(pipeline, /input message:/) assert.doesNotMatch(pipeline, /\u786e\u8ba4\u90e8\u7f72/) }) test('production deploy reports one aggregated result to WeCom', () => { const sourceCommitIndex = pipeline.indexOf("env.SOURCE_COMMIT = sh(script: 'git rev-parse HEAD'") const productionGuardIndex = pipeline.indexOf("if (env.RESOLVED_ENVIRONMENT == 'production')") assert.match(pipeline, /credentialsId: 'wecom-prod-webhook'/) assert.match(pipeline, /node\('java-builder'\) \{[\s\S]*?WECOM_WEBHOOK/) assert.match(pipeline, /env\.JOB_BASE_NAME == 'backend-java-prod' && params\.DEPLOY/) assert.ok(sourceCommitIndex >= 0, 'source commit must be recorded for notifications') assert.ok(sourceCommitIndex < productionGuardIndex, 'source commit must be recorded before production validation can fail') assert.match(pipeline, /def downstreamBuildUrls = \[:\]/) assert.match(pipeline, /downstreamBuildUrls\[service\] = downstreamBuild\.absoluteUrl/) assert.match(pipeline, /downstreamBuildNumbers\[service\] = downstreamBuild\.number/) assert.match(pipeline, /unstable \{[\s\S]*?'\u26a0\ufe0f \u751f\u4ea7\u90e8\u7f72\u90e8\u5206\u5931\u8d25'/) assert.match(pipeline, /\u7ed3\u679c\uff1a\u6210\u529f \$\{successCount\} \/ \u5931\u8d25 \$\{failedServices\.size\(\)\}/) assert.match(pipeline, /\u5931\u8d25\u670d\u52a1\uff1a\$\{failedLinks/) assert.match(pipeline, /failure \{[\s\S]*?'\u274c \u751f\u4ea7\u53d1\u5e03\u5931\u8d25'/) assert.match(pipeline, /aborted \{[\s\S]*?'\u23f9 \u751f\u4ea7\u90e8\u7f72\u5df2\u53d6\u6d88'/) assert.match(pipeline, /> \u8017\u65f6\uff1a\$\{duration\}/) assert.match(pipeline, /\u4f01\u4e1a\u5fae\u4fe1\u901a\u77e5\u5931\u8d25\uff0c\u4e0d\u5f71\u54cd\u539f\u6784\u5efa\u7ed3\u679c/) assert.doesNotMatch(servicePipeline, /wecom-prod-webhook|sendProductionWeComNotification/) })