import assert from 'node:assert/strict' import { readFile } from 'node:fs/promises' import test from 'node:test' const pipeline = (await readFile(new URL('../Jenkinsfile.service', import.meta.url), 'utf8')) .replaceAll('\r\n', '\n') const coordinatorPipeline = (await readFile(new URL('../Jenkinsfile', import.meta.url), 'utf8')) .replaceAll('\r\n', '\n') const directDeployScript = (await readFile(new URL('../deploy/scripts/deploy-built-service.sh', import.meta.url), 'utf8')) .replaceAll('\r\n', '\n') const ciMavenSettings = (await readFile(new URL('../deploy/maven/settings-ci.xml', import.meta.url), 'utf8')) .replaceAll('\r\n', '\n') const serviceDockerfile = (await readFile(new URL('../deploy/docker/Dockerfile', import.meta.url), 'utf8')) .replaceAll('\r\n', '\n') const serviceCompose = (await readFile(new URL('../deploy/docker-compose.yml', import.meta.url), 'utf8')) .replaceAll('\r\n', '\n') const overseasCompose = (await readFile(new URL('../deploy/docker-compose.overseas.yml', import.meta.url), 'utf8')) .replaceAll('\r\n', '\n') test('service pipeline keeps boolean operators on parseable Groovy continuation lines', () => { assert.doesNotMatch(pipeline, /^\s*(?:&&|\|\|)/m) }) test('test deployments compile packages without rerunning the full unit test suite', () => { assert.match(pipeline, /params\.ENVIRONMENT == 'test' \|\| params\.SKIP_TESTS/) assert.match(pipeline, /skipTestExecution \? '-DskipTests' : ''/) assert.match(pipeline, /-pl \$\{env\.RESOLVED_SERVICE\} -am clean package/) assert.doesNotMatch(pipeline, /-Dmaven\.test\.skip=true/) }) test('coordinator jobs present their own deployment environment as the default choice', () => { assert.match(coordinatorPipeline, /jobBaseName == 'backend-java-dev'/) assert.match(coordinatorPipeline, /\? \['development', 'test', 'staging', 'production'\]/) assert.match(coordinatorPipeline, /: \['test', 'development', 'staging', 'production'\]/) assert.match(coordinatorPipeline, /environmentChoicesForJob\(env\.JOB_BASE_NAME\)/) assert.match(coordinatorPipeline, /jobBaseName == 'backend-java-dev'[\s\S]*?'baiyue-firstleg-dev-'/) assert.match(coordinatorPipeline, /downstreamJobPrefixForJob\(env\.JOB_BASE_NAME\)/) }) test('production deployments cannot skip tests', () => { assert.match(coordinatorPipeline, /env\.RESOLVED_ENVIRONMENT == 'production' && params\.SKIP_TESTS/) assert.match(coordinatorPipeline, /error\('生产环境禁止跳过测试'\)/) assert.match(pipeline, /生产环境手工构建禁止跳过测试/) }) test('Java builds use the shared builder pool and serialize incremental target deployments', () => { assert.match(pipeline, /agent \{ label 'java-builder' \}/) assert.match(pipeline, /\['development', 'test'\]\.contains\(params\.ENVIRONMENT\)/) assert.match(pipeline, /params\.ENVIRONMENT == 'development' \? 'java-dev' : 'java-test'/) assert.match(pipeline, /'TARGET_IS_LOCAL=true'/) assert.match(pipeline, /String deploySlotLabel = "firstleg-\$\{params\.ENVIRONMENT\}-deploy-slot"/) assert.match(pipeline, /lock\(label: deploySlotLabel, quantity: 1, inversePrecedence: true\)/) assert.match(pipeline, /deploy-built-service\.sh/) assert.doesNotMatch(pipeline, /dev_deploy|test_deploy|TARGET_SSH_KEY/) assert.match(directDeployScript, /docker pull "\$IMAGE"/) assert.match(directDeployScript, /run_ssh docker pull "\$IMAGE"/) assert.doesNotMatch(directDeployScript, /docker save "\$IMAGE"/) assert.match(directDeployScript, /up -d --no-deps --pull never/) assert.doesNotMatch(directDeployScript, /--remove-orphans/) assert.match(directDeployScript, /--pull never/) }) test('production only accepts skipped downstream tests for a coordinator-pinned commit', () => { assert.match(pipeline, /boolean coordinatorPinnedCommit = params\.SOURCE_COMMIT\?\.trim\(\) as boolean/) assert.match(pipeline, /params\.ENVIRONMENT == 'production' && params\.SKIP_TESTS && !coordinatorPinnedCommit/) assert.match(pipeline, /生产环境手工构建禁止跳过测试/) }) test('development and test deployment locks are acquired before target executors', () => { const directDeployStage = pipeline.slice( pipeline.indexOf("stage('部署开发或测试环境')"), pipeline.indexOf("stage('部署单个服务')") ) const lockIndex = directDeployStage.indexOf('lock(label: deploySlotLabel') const nodeIndex = directDeployStage.indexOf('node(targetLabel)') assert.match(directDeployStage, /agent none/) assert.match(directDeployStage, /String targetLabel = params\.ENVIRONMENT == 'development' \? 'java-dev' : 'java-test'/) assert.ok(lockIndex >= 0, 'deployment slot lock must exist') assert.ok(nodeIndex >= 0, 'target node allocation must exist') assert.ok(lockIndex < nodeIndex, 'deployment lock must be acquired before target node allocation') }) test('development exposes gateways for remote acceptance and keeps internal services on loopback', () => { assert.match(serviceCompose, /\$\{PUBLISHED_BIND_ADDRESS:-0\.0\.0\.0\}:\$\{GATEWAY_PORT:-9000\}:9000/) assert.match(overseasCompose, /\$\{PUBLISHED_BIND_ADDRESS:-0\.0\.0\.0\}:\$\{GATEWAY_OVERSEAS_PORT:-9300\}:9300/) assert.match(directDeployScript, /gateway\|gateway-overseas\) PUBLISHED_BIND_ADDRESS=0\.0\.0\.0/) assert.match(directDeployScript, /\*\) PUBLISHED_BIND_ADDRESS=127\.0\.0\.1/) assert.match(directDeployScript, /gateway\|gateway-overseas\) export PUBLISHED_BIND_ADDRESS=0\.0\.0\.0/) assert.match(directDeployScript, /\*\) export PUBLISHED_BIND_ADDRESS=127\.0\.0\.1/) }) test('parallel image publications isolate registry credentials per Jenkins job', () => { const imageStage = pipeline.slice( pipeline.indexOf("stage('构建并发布单服务镜像')"), pipeline.indexOf("stage('部署开发或测试环境')") ) const loginIndex = imageStage.indexOf('docker login') const buildIndex = imageStage.indexOf('docker build') const pushIndex = imageStage.indexOf('docker push') const logoutIndex = imageStage.indexOf('docker logout') const cleanupIndex = imageStage.indexOf('rm -rf "$DOCKER_CONFIG"') assert.match(imageStage, /String dockerConfigDir = "\$\{pwd\(tmp: true\)\}\/docker-auth"/) assert.match(imageStage, /withEnv\(\["DOCKER_CONFIG=\$\{dockerConfigDir\}"\]\)/) assert.ok(loginIndex >= 0 && loginIndex < buildIndex, 'registry login must happen before image build') assert.ok(buildIndex < pushIndex, 'image build must happen before image push') assert.ok(pushIndex < logoutIndex, 'registry logout must happen after image push') assert.ok(logoutIndex < cleanupIndex, 'temporary Docker credentials must be removed last') assert.doesNotMatch(imageStage, /\/home\/deploy\/\.docker\/config\.json/) }) test('parallel remote deployments keep env and compose assets isolated by project', () => { assert.match(directDeployScript, /REMOTE_ENV_NAME="\.env\.\$PROJECT_NAME"/) assert.match(directDeployScript, /REMOTE_COMPOSE_NAME="docker-compose\.\$PROJECT_NAME\.yml"/) assert.match(directDeployScript, /REMOTE_ENV_TMP="\$REMOTE_DIR\/\$REMOTE_ENV_NAME\.\$SUFFIX\.tmp"/) assert.match(directDeployScript, /REMOTE_COMPOSE_TMP="\$REMOTE_DIR\/\$REMOTE_COMPOSE_NAME\.\$SUFFIX\.tmp"/) assert.match(directDeployScript, /env_target="\$3"/) assert.match(directDeployScript, /compose_target="\$4"/) assert.match(directDeployScript, /mv -f "\$env_tmp" "\$env_target"/) assert.match(directDeployScript, /mv -f "\$compose_tmp" "\$compose_target"/) assert.match(directDeployScript, /env_file="\$7"/) assert.match(directDeployScript, /compose_file="\$8"/) assert.match(directDeployScript, /--env-file "\$env_file"/) assert.match(directDeployScript, /-f "\$compose_file"/) assert.doesNotMatch(directDeployScript, /mv -f "\$env_tmp" "\$remote_dir\/\.env"/) assert.doesNotMatch(directDeployScript, /mv -f "\$compose_tmp" "\$remote_dir\/docker-compose\.yml"/) assert.doesNotMatch(directDeployScript, /--env-file \.env/) assert.doesNotMatch(directDeployScript, /-f docker-compose\.yml/) }) test('shared builders use a reachable public Maven mirror instead of the retired private proxy', () => { assert.match(pipeline, /MAVEN_SETTINGS = 'deploy\/maven\/settings-ci\.xml'/) assert.match(ciMavenSettings, /https:\/\/maven\.aliyun\.com\/repository\/public/) assert.doesNotMatch(ciMavenSettings, /:18081/) }) test('Java service images use the internal registry base image by default', () => { const internalBaseImage = '172.31.198.7:10082/java-project/eclipse-temurin:17-jre-alpine' assert.match(coordinatorPipeline, new RegExp(internalBaseImage.replaceAll('.', '\\.'))) assert.match(pipeline, new RegExp(internalBaseImage.replaceAll('.', '\\.'))) assert.match(serviceDockerfile, new RegExp(internalBaseImage.replaceAll('.', '\\.'))) assert.doesNotMatch(coordinatorPipeline, /docker\.m\.daocloud\.io\/library\/eclipse-temurin/) assert.doesNotMatch(pipeline, /docker\.m\.daocloud\.io\/library\/eclipse-temurin/) assert.doesNotMatch(serviceDockerfile, /docker\.m\.daocloud\.io\/library\/eclipse-temurin/) }) test('source workspaces are preserved for incremental Git fetches', () => { const buildStage = pipeline.slice( pipeline.indexOf("stage('构建并发布单服务')"), pipeline.indexOf("stage('部署开发或测试环境')") ) const directDeployStage = pipeline.slice( pipeline.indexOf("stage('部署开发或测试环境')"), pipeline.indexOf("stage('部署单个服务')") ) assert.match(pipeline, /checkout scm/) assert.doesNotMatch(buildStage, /cleanWs\(/) assert.match(directDeployStage, /cleanWs\(/) assert.equal((pipeline.match(/cleanWs\(/g) ?? []).length, 2) assert.doesNotMatch(coordinatorPipeline, /cleanWs\(/) }) test('direct deployments wait for health and automatically restore the previous image', () => { assert.match(directDeployScript, /HEALTH_TIMEOUT_SECONDS="\$\{DEPLOY_HEALTH_TIMEOUT_SECONDS:-240\}"/) assert.match(directDeployScript, /previous_image="\$\(local_container_image \|\| true\)"/) assert.match(directDeployScript, /if wait_for_local_health; then/) assert.match(directDeployScript, /开始自动回滚到/) assert.match(directDeployScript, /自动回滚成功/) assert.match(directDeployScript, /自动回滚失败/) assert.match(directDeployScript, /return 1/) }) test('one event bridge build deploys both regional instances in development and test', () => { assert.match(pipeline, /choice\(name: 'EVENT_BRIDGE_REGION', choices: \['BOTH', 'CN', 'OVERSEAS', 'SELECT'\]/) assert.match(pipeline, /\['BOTH', 'SELECT'\]\.contains\(params\.EVENT_BRIDGE_REGION\)/) assert.match(pipeline, /\? \['CN', 'OVERSEAS'\]/) assert.match(pipeline, /bridgeRegions\.each \{ bridgeRegion ->/) assert.match(pipeline, /EVENT_BRIDGE_REGION_OVERRIDE=\$\{bridgeRegion\}/) assert.match(pipeline, /部署事件桥实例:/) assert.match(pipeline, /imageTag\?\.endsWith\('-both'\)/) assert.match(pipeline, /imageTag\.substring\(0, imageTag\.length\(\) - '-both'\.length\(\)\)/) assert.match(pipeline, /env\.DEPLOY_BOTH_EVENT_BRIDGES = deployBothBridges \? 'true' : 'false'/) })