/** @jest-environment node */ import { mkdir, mkdtemp, readFile, rm, writeFile } from 'fs/promises' import { existsSync, readFileSync } from 'fs' import { spawnSync } from 'child_process' import os from 'os' import path from 'path' function extractBashFunction(source: string, name: string): string { const match = source.match(new RegExp(`(?:^|\\n)${name}\\(\\) \\{[\\s\\S]*?\\n\\}`, 'm')) if (!match) { throw new Error(`Bash function not found: ${name}`) } return match[0].trimStart() } function findBash(): string | null { const candidates = process.platform === 'win32' ? [ 'C:\\Program Files\\Git\\bin\\bash.exe', 'C:\\Program Files\\Git\\usr\\bin\\bash.exe', ] : ['/bin/bash', '/usr/bin/bash'] return candidates.find(existsSync) || null } function toBashPath(value: string): string { if (process.platform !== 'win32') return value const match = value.match(/^([A-Za-z]):[\\/](.*)$/) if (!match) return value.replace(/\\/g, '/') return `/${match[1].toLowerCase()}/${match[2].replace(/\\/g, '/')}` } function runBashGate( bash: string, functions: string, invocation: string, variables: Record, ) { return spawnSync(bash, ['-s'], { cwd: process.cwd(), encoding: 'utf8', input: `set -u\n${functions}\n${invocation}\n`, env: { ...process.env, MSYS2_ARG_CONV_EXCL: '*', MSYS_NO_PATHCONV: '1', ...variables, }, }) } describe('production video transcode drain gate', () => { let script: string beforeAll(async () => { script = (await readFile(path.join(process.cwd(), 'scripts/git-incremental-deploy.ps1'), 'utf8')) .replace(/\r\n/g, '\n') }) test('默认走单槽直接替换:同名停旧、启新,健康失败时恢复旧镜像', () => { const strategy = script.indexOf("[ValidateSet('direct')]") const directFunction = script.indexOf('direct_replace_active_container() {') const directBranch = script.indexOf('direct_replace_active_container "$CURRENT_CONTAINER"') const legacyDrain = script.indexOf('for TRANSCODE_DRAIN_SOURCE in $TRANSCODE_DRAIN_SOURCES; do') const directBody = extractBashFunction(script, 'direct_replace_active_container') const restoreBody = extractBashFunction(script, 'restore_direct_previous_container') const retireBody = extractBashFunction(script, 'retire_inactive_direct_slot') expect(strategy).toBeGreaterThan(0) expect(script).toContain("[string]$DeploymentStrategy = 'direct'") expect(directFunction).toBeGreaterThan(0) expect(directBranch).toBeGreaterThan(0) expect(directBranch).toBeLessThan(legacyDrain) expect(directBody).toContain('docker stop --time 10 "$name"') expect(directBody).toContain('docker rm "$name"') expect(directBody).toContain('create_container "$name" "$REGISTRY_COMMIT" 0') expect(directBody).toContain('fail_direct_replace_with_restore') expect(directBody).toContain('retire_inactive_direct_slot "$name"') expect(directBody).toContain('rm -f -- "$(transcode_drain_file "$name")" "$(task_detail_drain_file "$name")"') expect(restoreBody).toContain('create_container "$name" "$previous_image" 0') expect(restoreBody).toContain('verify_proxy_target "$name"') expect(retireBody).toContain('docker stop --time 10 "$inactive_name"') expect(retireBody).toContain('docker rm "$inactive_name"') }) test('复用镜像只接受已发布的同一精确 SHA,且不触发 Docker 构建或推送', () => { const reuse = script.indexOf('if [ "$REUSE_VERIFIED_IMAGE" = "1" ]; then') const build = script.indexOf('start_remote_step "Docker Build"') const verify = script.indexOf('start_remote_step "Verify Existing Image"') const identity = script.indexOf('resolve_published_image_identity() {') expect(script).toContain('[switch]$ReuseVerifiedImage') expect(script).toContain('-ReuseVerifiedImage cannot be combined with -SkipBuild, -SkipPush, -SkipDeploy, or -NoCache.') expect(script).toContain('docker pull "$LOCAL_REGISTRY_COMMIT"') expect(script).toContain('Existing exact-SHA image does not contain the requested Git revision') expect(script).toContain('docker tag "$LOCAL_REGISTRY_COMMIT" "$REGISTRY_TAG"') expect(reuse).toBeGreaterThan(0) expect(verify).toBeGreaterThan(reuse) expect(build).toBeGreaterThan(verify) expect(identity).toBeGreaterThan(build) }) test('先设置旧容器两类后台 worker drain 并确认 inactive,再启动候选容器', () => { const drain = script.indexOf('request_video_transcode_drain "$TRANSCODE_DRAIN_SOURCE"') const taskDetailDrain = script.indexOf('request_task_detail_upload_drain "$TRANSCODE_DRAIN_SOURCE"', drain) const candidate = script.indexOf('log "Starting candidate container $NEXT_CONTAINER"') expect(drain).toBeGreaterThan(0) expect(taskDetailDrain).toBeGreaterThan(drain) expect(candidate).toBeGreaterThan(taskDetailDrain) expect(script).toContain('wait_for_video_transcode_drain "$name"') expect(script).toContain('wait_for_task_detail_upload_drain "$name"') }) test('蓝绿入口存在时仍逐个排空意外运行的直连容器,且绝不强制删除', () => { const discoverCurrent = script.indexOf('if ! CURRENT_CONTAINER="$(discover_current_container)"; then') const collectDirect = script.indexOf('if [ -n "$LEGACY_IMAGE" ] && container_running "$CONTAINER_NAME"; then', discoverCurrent) const drainLoop = script.indexOf('for TRANSCODE_DRAIN_SOURCE in $TRANSCODE_DRAIN_SOURCES; do', collectDirect) const verifyLease = script.indexOf('verify_transcode_lease_schema "$TRANSCODE_DRAIN_SOURCE"', drainLoop) const requestDrain = script.indexOf('request_video_transcode_drain "$TRANSCODE_DRAIN_SOURCE"', verifyLease) const candidate = script.indexOf('log "Starting candidate container $NEXT_CONTAINER"', requestDrain) const finalDirectRemoval = script.lastIndexOf('remove_video_container_safely "$CONTAINER_NAME"') expect(collectDirect).toBeGreaterThan(discoverCurrent) expect(drainLoop).toBeGreaterThan(collectDirect) expect(verifyLease).toBeGreaterThan(drainLoop) expect(requestDrain).toBeGreaterThan(verifyLease) expect(candidate).toBeGreaterThan(requestDrain) expect(finalDirectRemoval).toBeGreaterThan(candidate) expect(script).toContain('TRANSCODE_DRAIN_SOURCES="${TRANSCODE_DRAIN_SOURCES} ${CONTAINER_NAME}"') expect(script).toContain('Legacy direct container could not be proven inactive and was preserved') expect(script).not.toContain('docker rm -f "$CONTAINER_NAME"') }) test('正式发布发现任何运行中的影子容器时,在触碰生产入口前 fail-closed', () => { const shadowDiscovery = script.indexOf("RUNNING_CONTAINER_METADATA=\"$(docker ps --format") const shadowFailure = script.indexOf('Running shadow container(s) detected; production deployment is blocked', shadowDiscovery) const directTopologyGate = script.indexOf('LEGACY_IMAGE="$(docker inspect', shadowFailure) const proxyMutation = script.indexOf('mkdir -p "$PROXY_ROOT"', directTopologyGate) const currentDiscovery = script.indexOf('CURRENT_CONTAINER="$(discover_current_container)"', proxyMutation) expect(shadowDiscovery).toBeGreaterThan(0) expect(shadowFailure).toBeGreaterThan(shadowDiscovery) expect(directTopologyGate).toBeGreaterThan(shadowFailure) expect(proxyMutation).toBeGreaterThan(directTopologyGate) expect(currentDiscovery).toBeGreaterThan(proxyMutation) expect(script).toContain('if [ "$TARGET" = "production" ]; then') expect(script).toContain('$1 == "youyidian-3d-material-shadow"') expect(script).toContain('$1 ~ /^youyidian-3d-shadow($|[-_])/') expect(script).toContain('{{.Label "com.docker.compose.service"}}') expect(script).toContain('$2 == "app-shadow"') expect(script).toContain('Cannot inspect running containers for shadow topology') expect(script).toContain('until they are stopped and verified') }) test('uploads 路径必须显式落入受控 allowlist,默认锁定当前正式 ZFS 数据集', () => { expect(script).toContain("$defaultUploadsMount = '/mnt/PoolOne/youyidian-uploads'") expect(script).toContain("'/mnt/PoolOne/apps/youyidian-3d/uploads'") expect(script).toContain('[string]$UploadsMount = $env:GONGXIANGP2_UPLOADS_MOUNT') expect(script).toContain('$trustedUploadsMounts -cnotcontains $UploadsMount') expect(script).toContain("'__UPLOADS_MOUNT__' = ConvertTo-ShellSingleQuoted $UploadsMount") expect(script).not.toContain("UPLOADS_MOUNT='/mnt/PoolOne/apps/youyidian-3d/uploads'") }) test('findmnt 模拟拒绝根盘目录壳、多行或未受信 tuple,并接受两条显式路径', () => { const bash = findBash() expect(bash).not.toBeNull() if (!bash) return const helper = extractBashFunction(script, 'uploads_findmnt_metadata_is_trusted') const invoke = 'uploads_findmnt_metadata_is_trusted "$CASE_PATH" "$CASE_MOUNT" "$CASE_ROOT"' const run = (casePath: string, mount: string, root = '/ /dev/sda2 ext4') => runBashGate( bash, helper, invoke, { CASE_PATH: casePath, CASE_MOUNT: mount, CASE_ROOT: root }, ) expect(run( '/mnt/PoolOne/youyidian-uploads', '/mnt/PoolOne/youyidian-uploads PoolOne/youyidian-uploads zfs', ).status).toBe(0) expect(run( '/mnt/PoolOne/apps/youyidian-3d/uploads', '/mnt/PoolOne PoolOne zfs', ).status).toBe(0) expect(run( '/mnt/PoolOne/youyidian-uploads', '/ /dev/sda2 ext4', ).status).not.toBe(0) expect(run( '/mnt/PoolOne/youyidian-uploads', '/mnt/PoolOne/youyidian-uploads PoolOne/youyidian-uploads ext4', ).status).not.toBe(0) expect(run( '/mnt/PoolOne/youyidian-uploads', '/mnt/PoolOne/youyidian-uploads PoolOne/youyidian-uploads zfs\n/ /dev/sda2 ext4', ).status).not.toBe(0) }) test('Docker Mounts 模拟拒绝 volume、错 source、只读和重复 /app/uploads;旧容器仅可用已解析到同一数据盘的别名', () => { const bash = findBash() expect(bash).not.toBeNull() if (!bash) return const helper = extractBashFunction(script, 'uploads_container_mount_metadata_is_trusted') const invoke = 'uploads_container_mount_metadata_is_trusted "$EXPECTED_SOURCE" "$CASE_METADATA"' const expectedSource = '/mnt/PoolOne/youyidian-uploads' const run = (metadata: string) => runBashGate( bash, helper, invoke, { EXPECTED_SOURCE: expectedSource, CASE_METADATA: metadata }, ) expect(run(`/app/uploads\tbind\t${expectedSource}\ttrue`).status).toBe(0) expect(run('/app/uploads\tvolume\tuploads-volume\ttrue').status).not.toBe(0) expect(run('/app/uploads\tbind\t/mnt/PoolOne/apps/youyidian-3d/uploads\ttrue').status).not.toBe(0) expect(run(`/app/uploads\tbind\t${expectedSource}\tfalse`).status).not.toBe(0) expect(run( `/app/uploads\tbind\t${expectedSource}\ttrue\n/app/uploads\tbind\t${expectedSource}\ttrue`, ).status).not.toBe(0) const legacySource = '/mnt/PoolOne/apps/youyidian-3d/uploads' const aliasHelper = ` readlink() { if [ "$3" = "${legacySource}" ]; then printf '%s\\n' "${expectedSource}" else printf '%s\\n' "$3" fi } ${helper}` const runWithResolvedAlias = (allowAlias: string) => runBashGate( bash, aliasHelper, 'uploads_container_mount_metadata_is_trusted "$EXPECTED_SOURCE" "$CASE_METADATA" "$ALLOW_ALIAS"', { EXPECTED_SOURCE: expectedSource, CASE_METADATA: `/app/uploads\tbind\t${legacySource}\ttrue`, ALLOW_ALIAS: allowAlias, }, ) expect(runWithResolvedAlias('0').status).not.toBe(0) expect(runWithResolvedAlias('1').status).toBe(0) }) test('statfs 模拟必须拒绝宿主根盘签名和宿主/容器不一致', () => { const bash = findBash() expect(bash).not.toBeNull() if (!bash) return const helpers = [ extractBashFunction(script, 'host_statfs_signature_is_trusted'), extractBashFunction(script, 'statfs_signatures_match'), ].join('\n') const runHost = (uploads: string, root: string) => runBashGate( bash, helpers, 'host_statfs_signature_is_trusted "$UPLOADS_SIGNATURE" "$ROOT_SIGNATURE"', { UPLOADS_SIGNATURE: uploads, ROOT_SIGNATURE: root }, ) const runContainer = (host: string, container: string) => runBashGate( bash, helpers, 'statfs_signatures_match "$HOST_SIGNATURE" "$CONTAINER_SIGNATURE"', { HOST_SIGNATURE: host, CONTAINER_SIGNATURE: container }, ) expect(runHost('a1b2:2fc12fc1', '9abc:ef53').status).toBe(0) expect(runHost('9abc:ef53', '9abc:ef53').status).not.toBe(0) expect(runContainer('a1b2:2fc12fc1', 'a1b2:2fc12fc1').status).toBe(0) expect(runContainer('a1b2:2fc12fc1', 'ffff:2fc12fc1').status).not.toBe(0) }) test('宿主门禁先于所有容器/流量动作,候选严格 create→inspect→UPLOAD_DIR→start→statfs→health→复验→切流', () => { const hostGate = script.indexOf('if ! verify_host_uploads_mount; then') const firstContainerDiscovery = script.indexOf('RUNNING_CONTAINER_METADATA="$(docker ps --format', hostGate) const firstLegacyInspect = script.indexOf('LEGACY_IMAGE="$(docker inspect', hostGate) const drainLoop = script.indexOf('for TRANSCODE_DRAIN_SOURCE in $TRANSCODE_DRAIN_SOURCES; do', firstLegacyInspect) const existingStorage = script.indexOf('verify_container_uploads_storage "$TRANSCODE_DRAIN_SOURCE"', drainLoop) const existingDrain = script.indexOf('request_video_transcode_drain "$TRANSCODE_DRAIN_SOURCE"', existingStorage) const create = script.indexOf('create_container "$NEXT_CONTAINER" "$REGISTRY_COMMIT"') const mountInspect = script.indexOf('verify_container_uploads_mount_metadata "$NEXT_CONTAINER"', create) const uploadDirInspect = script.indexOf('verify_container_upload_dir_config "$NEXT_CONTAINER" 1', mountInspect) const start = script.indexOf('docker start "$NEXT_CONTAINER"', uploadDirInspect) const statfs = script.indexOf('verify_container_uploads_statfs "$NEXT_CONTAINER"', start) const uploadDirRuntime = script.indexOf('verify_container_upload_dir_runtime "$NEXT_CONTAINER" 1', statfs) const health = script.indexOf('wait_for_container_health "$NEXT_CONTAINER"', uploadDirRuntime) const writeProxy = script.indexOf('write_proxy_config "${NEXT_IP}:3000" "$NEXT_CONTAINER"', health) const finalStorage = script.indexOf('verify_container_uploads_storage "$NEXT_CONTAINER"', writeProxy) const switchTraffic = script.indexOf('if ! switch_proxy; then', finalStorage) expect(hostGate).toBeGreaterThan(0) expect(firstContainerDiscovery).toBeGreaterThan(hostGate) expect(firstLegacyInspect).toBeGreaterThan(hostGate) expect(existingStorage).toBeGreaterThan(drainLoop) expect(existingDrain).toBeGreaterThan(existingStorage) expect(create).toBeGreaterThan(existingDrain) expect(mountInspect).toBeGreaterThan(create) expect(uploadDirInspect).toBeGreaterThan(mountInspect) expect(start).toBeGreaterThan(uploadDirInspect) expect(statfs).toBeGreaterThan(start) expect(uploadDirRuntime).toBeGreaterThan(statfs) expect(health).toBeGreaterThan(uploadDirRuntime) expect(finalStorage).toBeGreaterThan(writeProxy) expect(switchTraffic).toBeGreaterThan(finalStorage) for (const name of [ 'uploads_findmnt_metadata_is_trusted', 'host_statfs_signature_is_trusted', 'statfs_signatures_match', 'uploads_container_mount_metadata_is_trusted', 'verify_host_uploads_mount', 'verify_container_uploads_mount_metadata', 'verify_container_uploads_statfs', 'verify_container_upload_dir_config', 'verify_container_upload_dir_runtime', 'restore_unreloaded_proxy_config', ]) { expect(extractBashFunction(script, name)).not.toContain('|| true') } const rejectCandidate = extractBashFunction(script, 'fail_candidate_before_traffic') expect(rejectCandidate).not.toContain('|| true') expect(rejectCandidate.indexOf('restore_unreloaded_proxy_config')).toBeLessThan( rejectCandidate.indexOf('remove_video_container_safely "$NEXT_CONTAINER"'), ) }) test('应用上传根目录必须由容器参数钉死到已验证的 /app/uploads', () => { const createContainer = extractBashFunction(script, 'create_container') const verifyConfig = extractBashFunction(script, 'verify_container_upload_dir_config') const verifyRuntime = extractBashFunction(script, 'verify_container_upload_dir_runtime') expect(createContainer.indexOf('--env-file "$ENV_FILE_PATH"')).toBeLessThan( createContainer.indexOf('-e "UPLOAD_DIR=/app/uploads"'), ) expect(verifyConfig).toContain('Container ${name} must declare exactly UPLOAD_DIR=/app/uploads') expect(verifyConfig).toContain('Existing container ${name} has an unsafe UPLOAD_DIR override') expect(verifyRuntime).toContain('if (resolved !== "/app/uploads") process.exit(1)') expect(script).toContain('verify_container_uploads_storage "$NEXT_CONTAINER" 1') }) test('上传路径关闭请求缓冲,并按 128MiB 上传与 20MiB 分片设置独立余量', () => { const exactStart = script.indexOf('location = /api/materials/upload {') const chunkStart = script.indexOf('location = /api/materials/upload/chunk {', exactStart) const uploadPrefixStart = script.indexOf('location ^~ /api/materials/upload/ {', chunkStart) const fallbackStart = script.indexOf('location / {', uploadPrefixStart) const exactLocation = script.slice(exactStart, chunkStart) const chunkLocation = script.slice(chunkStart, uploadPrefixStart) const uploadPrefixLocation = script.slice(uploadPrefixStart, fallbackStart) const globalLimitMatch = script.match(/http \{[\s\S]*?client_max_body_size (\d+)m;/) const exactLimitMatch = exactLocation.match(/client_max_body_size (\d+)m;/) const chunkLimitMatch = chunkLocation.match(/client_max_body_size (\d+)m;/) const uploadPrefixLimitMatch = uploadPrefixLocation.match(/client_max_body_size (\d+)m;/) expect(exactStart).toBeGreaterThan(0) expect(chunkStart).toBeGreaterThan(exactStart) expect(uploadPrefixStart).toBeGreaterThan(chunkStart) expect(fallbackStart).toBeGreaterThan(uploadPrefixStart) expect(exactLocation).toContain('proxy_request_buffering off;') expect(chunkLocation).toContain('proxy_request_buffering off;') expect(uploadPrefixLocation).toContain('proxy_request_buffering off;') expect(Number(exactLimitMatch?.[1])).toBe(129) expect(Number(chunkLimitMatch?.[1])).toBe(21) expect(Number(uploadPrefixLimitMatch?.[1])).toBe(2) expect(Number(globalLimitMatch?.[1])).toBe(16) }) test('公开钉钉免登入口在代理层限制为 64KiB', () => { const loginStart = script.indexOf('location = /api/dingtalk/h5-login {') const nextLocation = script.indexOf('location = /api/materials/upload {', loginStart) const loginLocation = script.slice(loginStart, nextLocation) expect(loginStart).toBeGreaterThan(0) expect(nextLocation).toBeGreaterThan(loginStart) expect(loginLocation).toContain('client_max_body_size 64k;') expect(loginLocation).toContain('proxy_request_buffering off;') }) test('候选切流前必须同时验证健康依赖和精确部署提交', () => { const readiness = script.indexOf('health_payload_is_ready() {') const candidateHealth = script.indexOf('wait_for_container_health "$NEXT_CONTAINER" "$COMMIT_SHA"') const firstSwitch = script.indexOf('switch_proxy; then', candidateHealth) expect(readiness).toBeGreaterThan(0) expect(candidateHealth).toBeGreaterThan(readiness) expect(firstSwitch).toBeGreaterThan(candidateHealth) expect(script).toContain("grep -Fq -- '\"status\":\"healthy\"'") expect(script).toContain("grep -Fq -- '\"database\":{\"status\":\"ok\"'") expect(script).toContain("grep -Fq -- '\"redis\":{\"status\":\"ok\"'") expect(script).toContain('"\\"deploymentCommit\\":\\"${expected_commit}\\""') expect(script).toContain('verify_proxy_target "$NEXT_CONTAINER" "$COMMIT_SHA"') expect(script).toContain('container_healthy "$NEXT_CONTAINER" "$COMMIT_SHA"') expect(script).toContain('listen [::]:${PORT} ipv6only=on;') expect(script).toContain('runtime_proxy_container_at_url "http://[::1]:${PORT}/api/health"') expect(script).toContain("curl --noproxy '*' -g -fsS --max-time 8 \"http://[::1]:${PORT}/api/health\"") }) test('公网验收必须证明候选、精确提交、健康依赖、S3 与两张 Logo', () => { const publicProbe = script.indexOf('Checking public FRP, authenticated S3, and Logo decode acceptance under the deployment lock') const committedTarget = script.indexOf('COMMITTED_PUBLIC_TARGET="$(tr -d', publicProbe) const payloadGate = script.indexOf('health_payload_is_ready /tmp/git-deploy-public-health.json "$COMMIT_SHA"', committedTarget) const targetGate = script.indexOf('[ "$public_target_count" = "1" ] && [ "$public_target" = "$COMMITTED_PUBLIC_TARGET" ]', payloadGate) const s3Gate = script.indexOf('https://s3.jiansun.vip/minio/health/live', targetGate) const settings = script.indexOf('$PUBLIC_APP_URL/api/system-settings', s3Gate) const configuredLogoValidation = script.indexOf("settings.headerLogoUrl, settings.loginLogoUrl", settings) const configuredLogoFetch = script.indexOf('done { const candidateHealth = script.indexOf('wait_for_container_health "$NEXT_CONTAINER" "$COMMIT_SHA"') const databaseIdentity = script.indexOf('verify_candidate_database_identity "$NEXT_CONTAINER"', candidateHealth) const brandAndStorage = script.indexOf('verify_candidate_brand_and_storage_dependencies "$NEXT_CONTAINER" "$NEXT_IP"', databaseIdentity) const capability = script.indexOf('verify_video_transcode_capability "$NEXT_CONTAINER"', brandAndStorage) const switchTraffic = script.indexOf('if ! switch_proxy; then', capability) expect(candidateHealth).toBeGreaterThan(0) expect(databaseIdentity).toBeGreaterThan(candidateHealth) expect(brandAndStorage).toBeGreaterThan(databaseIdentity) expect(capability).toBeGreaterThan(brandAndStorage) expect(switchTraffic).toBeGreaterThan(capability) expect(script).toContain('new HeadBucketCommand({ Bucket: process.env.S3_BUCKET })') expect(script).toContain('verify_candidate_baked_brand_assets "$name"') expect(script).toContain('verify_candidate_configured_brand_assets "$name" "$candidate_ip"') }) test('构建机在任何拉代码或发布动作前必须证明 Node 可用', () => { const preflight = script.indexOf('start_remote_step "Preflight"') const requireNode = script.indexOf('require_cmd node', preflight) const gitSync = script.indexOf('start_remote_step "Git Sync"', requireNode) expect(preflight).toBeGreaterThan(0) expect(requireNode).toBeGreaterThan(preflight) expect(gitSync).toBeGreaterThan(requireNode) }) test('生产镜像来源绑定 exact SHA,运行时环境变量不能冒充新代码', () => { const dockerfile = readFileSync(path.join(process.cwd(), 'Dockerfile'), 'utf8') const buildArg = script.indexOf('--build-arg "VCS_REF=$COMMIT_SHA"') const productionLabel = script.indexOf('Production image revision label matches $COMMIT_SHA') const candidateLabel = script.indexOf('verify_image_revision_label "$REGISTRY_COMMIT" "$COMMIT_SHA"') const candidateFile = script.indexOf('verify_container_baked_revision "$NEXT_CONTAINER" "$COMMIT_SHA"') const candidateHealth = script.indexOf('wait_for_container_health "$NEXT_CONTAINER" "$COMMIT_SHA"') expect(script).toContain('Production deployment requires a freshly built and pushed exact-SHA image') expect(buildArg).toBeGreaterThan(0) expect(productionLabel).toBeGreaterThan(buildArg) expect(candidateLabel).toBeGreaterThan(productionLabel) expect(candidateFile).toBeGreaterThan(candidateLabel) expect(candidateHealth).toBeGreaterThan(candidateFile) expect(dockerfile).toContain('LABEL org.opencontainers.image.revision=$VCS_REF') expect(dockerfile).toContain('/app/DEPLOYMENT_COMMIT') expect(dockerfile).toContain('chmod 0444 /app/DEPLOYMENT_COMMIT') }) test('HTTP Gitea 只允许固定仓库和精确提交,SSH 通道禁止明文凭据与盲信主机', () => { expect(script).toContain("[string]$RepoUrl = 'http://git.1080p.hk/WuYongJie/gongxiangp2.git'") expect(script).toContain('Inline Git credentials are forbidden') expect(script).toContain("$trustedRepoUrl = 'http://git.1080p.hk/WuYongJie/gongxiangp2.git'") expect(script).toContain("if (-not $DryRun -and $Commit -cnotmatch '^[0-9a-f]{40}$')") expect(script).toContain('HTTP Git transport requires -Commit with the exact 40-character lowercase SHA') expect(script).toContain("[ \"$REPO_URL\" = 'http://git.1080p.hk/WuYongJie/gongxiangp2.git' ]") expect(script).not.toContain('https://git.1080p.hk') expect(script).toContain("-c 'http.lowSpeedLimit=1'") expect(script).toContain("-c 'http.lowSpeedTime=30'") expect(script).toContain('-c http.lowSpeedLimit=1') expect(script).toContain('-c http.lowSpeedTime=30') expect(script).toContain('StrictHostKeyChecking=yes') expect(script).toContain('-hostkey $BuildServerHostKey') expect(script).toContain("awk '!/^#/ { print $2 \" \" $3 }' | ssh-keygen -lf - -E sha256") expect(script).toContain('Production host key fingerprint does not match the pinned value') expect(script).not.toContain('StrictHostKeyChecking=no') expect(script).not.toContain('http.extraHeader') expect(script).not.toContain('__GIT_AUTH_HEADER__') expect(script).toContain('mkdir -m 700') expect(script).toContain("chmod 600 '$remoteTemp'") expect(script).toContain("test ! -L '$remoteTemp'") expect(script).toContain("test -O '$remoteTemp'") }) test('远端构建目录只能使用已验证的专用仓库,绝不删除未知目录', () => { expect(script).toContain("$trustedRemoteBuildDir = '/root/youyidian-3d-git-build'") expect(script).toContain('[ "$REMOTE_BUILD_DIR" = \'/root/youyidian-3d-git-build\' ]') expect(script).toContain('Pinned build directory is not the expected Git worktree; refusing destructive cleanup') expect(script).toContain('[ "$(realpath -e "$REMOTE_BUILD_DIR")" = "$REMOTE_BUILD_DIR" ]') expect(script).toContain('[ "$existing_origin" = "$REPO_URL" ]') expect(script).not.toContain('rm -rf "$REMOTE_BUILD_DIR"') expect(script).not.toContain('git remote set-url origin "$REPO_URL"') }) test('生产按不可变 manifest 拉取并用平台 config digest 跨 Docker 版本核对镜像', () => { const captureDigest = script.indexOf('EXPECTED_MANIFEST_DIGEST="$(curl -fsSI') const capturePlatform = script.indexOf('EXPECTED_PLATFORM_MANIFEST_DIGEST="$(printf', captureDigest) const captureId = script.indexOf('EXPECTED_IMAGE_ID="$(printf', capturePlatform) const immutableReference = script.indexOf('REGISTRY_COMMIT="${BUILD_SERVER_IP}:${REGISTRY_PORT}/${IMAGE_NAME}@${EXPECTED_MANIFEST_DIGEST}"', captureId) const remoteCompare = script.indexOf('ACTUAL_PROD_IMAGE_ID="$(prod_ssh', immutableReference) const payloadCompare = script.indexOf('ACTUAL_IMAGE_ID="$(docker image inspect', remoteCompare) const createCandidate = script.indexOf('create_container "$NEXT_CONTAINER" "$REGISTRY_COMMIT"', payloadCompare) expect(captureDigest).toBeGreaterThan(0) expect(capturePlatform).toBeGreaterThan(captureDigest) expect(captureId).toBeGreaterThan(capturePlatform) expect(script).toContain('application/vnd.oci.image.index.v1+json') expect(script).toContain('tolower($1) == "docker-content-digest:" { print $2 }') expect(script).not.toContain("awk -F ': *' 'tolower($1) == \"docker-content-digest\"") expect(script).toContain('entry.platform.os === "linux"') expect(script).toContain('entry.platform.architecture === "amd64"') expect(script).toContain('document.config && document.config.digest') expect(immutableReference).toBeGreaterThan(captureDigest) expect(remoteCompare).toBeGreaterThan(immutableReference) expect(payloadCompare).toBeGreaterThan(remoteCompare) expect(createCandidate).toBeGreaterThan(payloadCompare) expect(script).toContain('Production image ID differs from the image built on the pinned build server') expect(script).toContain('[ "$image_id" = "$EXPECTED_IMAGE_ID" ]') }) test('Docker 构建保留 Sharp/SWC optional 平台包且迁移 SQL 位于最终 context allowlist', () => { const dockerfile = readFileSync(path.join(process.cwd(), 'Dockerfile'), 'utf8') const dockerignore = readFileSync(path.join(process.cwd(), '.dockerignore'), 'utf8') const finalSqlIgnore = dockerignore.lastIndexOf('*.sql') const finalMigrationInclude = dockerignore.lastIndexOf('!prisma/migrations/**') expect(dockerfile).toContain('npm ci --prefer-offline') expect(dockerfile).toContain( 'npm_config_build_from_source=canvas npm_config_nodedir=/usr/local npm ci', ) expect(dockerfile).not.toContain('--omit=optional') expect(dockerfile).toContain("grep -Eq '^[0-9a-f]{40}$'") expect(dockerfile).toContain("const sharp=require('sharp')") expect(dockerfile).toContain('runner brand image decode smoke passed') expect(dockerfile).toContain("['header-logo.png','login-logo.png','favicon.png']") expect(finalMigrationInclude).toBeGreaterThan(finalSqlIgnore) }) ;(process.platform === 'win32' ? test : test.skip)( 'Target=shadow 在任何 Git 扫描或远程操作前 fail-closed', () => { const deployScript = path.join(process.cwd(), 'scripts', 'git-incremental-deploy.ps1') const powershell = path.join( process.env.SystemRoot || 'C:\\Windows', 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe', ) const deploy = spawnSync(powershell, [ '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', deployScript, '-Target', 'shadow', '-DryRun', ], { cwd: process.cwd(), encoding: 'utf8', timeout: 30_000 }) const output = `${deploy.stdout}\n${deploy.stderr}` expect(deploy.status).not.toBe(0) expect(output).toContain('Target=shadow is disabled fail-closed') expect(output).toContain('uploads/runtime/Redis isolation') expect(output).not.toContain('Resolving and scanning the exact Git tree') expect(output).not.toContain('Connecting to build server') }, 35_000, ) ;(process.platform === 'win32' ? test : test.skip)( 'HTTP Gitea 正式发布未提供完整提交 SHA 时在任何远程操作前 fail-closed', () => { const deployScript = path.join(process.cwd(), 'scripts', 'git-incremental-deploy.ps1') const powershell = path.join( process.env.SystemRoot || 'C:\\Windows', 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe', ) const deploy = spawnSync(powershell, [ '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', deployScript, ], { cwd: process.cwd(), encoding: 'utf8', timeout: 30_000 }) const output = `${deploy.stdout}\n${deploy.stderr}` expect(deploy.status).not.toBe(0) expect(output).toContain('HTTP Git transport requires -Commit with the exact 40-character lowercase SHA') expect(output).not.toContain('Resolving and scanning the exact Git tree') expect(output).not.toContain('Connecting to build server') }, 35_000, ) test('旧镜像不能证明协议能力时 fail-closed,候选容器不会启动', () => { expect(script).toContain("grep -Fqx -- 'VIDEO_TRANSCODE_DRAIN_PROTOCOL_V1'") expect(script).toContain('-e "VIDEO_TRANSCODE_INSTANCE_ID=$transcode_instance_id"') expect(script).toContain('grep -Fqx -- "instanceId=$instance_id"') expect(script).toContain('if ! verify_video_transcode_capability "$NEXT_CONTAINER"; then') expect(script).toContain('candidate was not started (first rollout fails closed)') expect(script).toContain('apply the additive Prisma migration before deployment') }) test('即使没有旧容器,候选容器也必须独立验证租约字段后才能切流', () => { const candidateSchema = script.indexOf('verify_transcode_lease_schema "$NEXT_CONTAINER"') const switchTraffic = script.indexOf('write_proxy_config "${NEXT_IP}:3000" "$NEXT_CONTAINER"') expect(candidateSchema).toBeGreaterThan(0) expect(switchTraffic).toBeGreaterThan(candidateSchema) expect(script).toContain('materials_transcode_lease_idx') expect(script).toContain('20260811_add_video_transcode_lease') expect(script).toContain('finished_at IS NOT NULL AND rolled_back_at IS NULL') expect(script).toContain('transcode_lease_token");') expect(script).toContain('token.data_type === "text" && token.is_nullable === "YES"') expect(script).toContain('expires.data_type === "timestamp without time zone" && expires.is_nullable === "YES"') expect(script).toContain('String(migrations[0].checksum || "") === sourceChecksum') expect(script).toContain('readFileSync(migrationPath, "utf8").replace(/\\r\\n/g, "\\n")') expect(script).toContain('!/\\bUNIQUE\\b|\\bWHERE\\b\/i.test(indexDefinition)') expect(script).toContain('!columnsOk || !indexOk || !migrationOk') expect(script).toContain('traffic was not switched') }) test('旧域名扫描绑定精确目标 SHA,并将同一 SHA 固定给远程构建', () => { const resolveRemote = script.indexOf("'ls-remote', '--heads', $RepositoryUrl, \"refs/heads/$TargetBranch\"") const localObjectReuse = script.indexOf("'clone', '--no-checkout', '--shared', $localRepository, $targetCheckout", resolveRemote) const fetchFallback = script.indexOf('"+refs/heads/${TargetBranch}:refs/preflight/branch"', localObjectReuse) const checkoutFallback = script.indexOf("'worktree', 'add', '--detach', $targetCheckout, $targetCommit", fetchFallback) const scanTarget = script.indexOf('& $nodeCommand.Source $scannerPath', checkoutFallback) const preflight = script.indexOf("Write-Info 'Resolving and scanning the exact Git tree that will be deployed...'") const bindCommit = script.indexOf("$tokens['__COMMIT__'] = ConvertTo-ShellSingleQuoted $resolvedTargetCommit") const renderRemote = script.indexOf('$remoteScript = $remoteTemplate') const remoteDeploy = script.indexOf('$exitCode = Invoke-BuildServerScript -ScriptText $remoteScript') const remoteTemplateStart = script.indexOf("$remoteTemplate = @'") const remoteTemplateEnd = script.indexOf("\n'@", remoteTemplateStart) const remoteTemplate = script.slice(remoteTemplateStart, remoteTemplateEnd) expect(script).toContain("Join-Path $PSScriptRoot 'check-legacy-storage-boundaries.mjs'") expect(script).toContain('Get-Command node -ErrorAction SilentlyContinue') expect(script).toContain('Legacy storage boundary preflight failed for target $targetCommit with exit code') expect(resolveRemote).toBeGreaterThan(0) expect(localObjectReuse).toBeGreaterThan(resolveRemote) expect(fetchFallback).toBeGreaterThan(localObjectReuse) expect(checkoutFallback).toBeGreaterThan(fetchFallback) expect(scanTarget).toBeGreaterThan(checkoutFallback) expect(preflight).toBeGreaterThan(0) expect(bindCommit).toBeGreaterThan(preflight) expect(renderRemote).toBeGreaterThan(bindCommit) expect(remoteDeploy).toBeGreaterThan(renderRemote) expect(remoteTemplate).not.toContain('Invoke-LegacyStorageBoundaryPreflight') expect(script.indexOf('function Invoke-LegacyStorageBoundaryPreflight')).toBeLessThan(remoteTemplateStart) expect(remoteTemplate).toContain('TARGET_REF="$COMMIT_INPUT"') expect(remoteTemplate).toContain('[ "$COMMIT_SHA" = "$COMMIT_INPUT" ]') }) ;(process.platform === 'win32' ? test : test.skip)( '分支最新树通过时,-Commit 指向含旧域名的旧提交仍会在远程操作前拒绝', async () => { const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'video-deploy-target-test-')) const repository = path.join(tempRoot, 'repository') const badSource = path.join(repository, 'app', 'deploy-target.ts') const scanner = path.join(process.cwd(), 'scripts', 'check-legacy-storage-boundaries.mjs') const deployScript = path.join(process.cwd(), 'scripts', 'git-incremental-deploy.ps1') const powershell = path.join( process.env.SystemRoot || 'C:\\Windows', 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe', ) const runGit = (...args: string[]) => { const result = spawnSync('git', args, { cwd: repository, encoding: 'utf8' }) if (result.status !== 0) { throw new Error(`git ${args.join(' ')} failed: ${result.stdout}\n${result.stderr}`) } return result.stdout.trim() } try { await mkdir(path.dirname(badSource), { recursive: true }) runGit('init') runGit('config', 'user.name', 'Codex Test') runGit('config', 'user.email', 'codex-test@example.invalid') await writeFile(badSource, "export const unsafeLogo = 'https://s3.1080p.hk/bucket/logo.png'\n", 'utf8') runGit('add', '.') runGit('-c', 'commit.gpgsign=false', 'commit', '-m', 'unsafe old target') const oldCommit = runGit('rev-parse', 'HEAD') await writeFile(badSource, "export const safeLogo = '/brand/header-logo.png'\n", 'utf8') runGit('add', '.') runGit('-c', 'commit.gpgsign=false', 'commit', '-m', 'safe branch head') runGit('branch', '-M', 'main') const latestScan = spawnSync(process.execPath, [scanner], { cwd: repository, encoding: 'utf8' }) expect(latestScan.status).toBe(0) const deploy = spawnSync(powershell, [ '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', deployScript, '-RepoUrl', repository, '-Branch', 'main', '-Commit', oldCommit, '-DryRun', '-BuildServerPassword', 'local-test-only', '-BuildServerProxyCommand', 'must-not-run', ], { cwd: process.cwd(), encoding: 'utf8', timeout: 60_000 }) const output = `${deploy.stdout}\n${deploy.stderr}` expect(deploy.status).not.toBe(0) expect(output).toContain('app/deploy-target.ts') expect(output).toContain('Legacy storage boundary preflight failed for target') expect(output).not.toContain('Cannot find plink.exe') } finally { await rm(tempRoot, { recursive: true, force: true }) } }, 70_000, ) test('候选容器切流前必须验证品牌与上传幂等数据库门禁', () => { const dockerfile = readFileSync(path.join(process.cwd(), 'Dockerfile'), 'utf8') const leaseSchema = script.indexOf('verify_transcode_lease_schema "$NEXT_CONTAINER"') const brandDefaults = script.indexOf('verify_brand_logo_defaults "$NEXT_CONTAINER"') const uploadIdempotency = script.indexOf('verify_material_upload_idempotency_schema "$NEXT_CONTAINER"') const switchTraffic = script.indexOf('write_proxy_config "${NEXT_IP}:3000" "$NEXT_CONTAINER"') expect(brandDefaults).toBeGreaterThan(leaseSchema) expect(uploadIdempotency).toBeGreaterThan(brandDefaults) expect(switchTraffic).toBeGreaterThan(uploadIdempotency) expect(script).toContain('normalize(defaults.get("headerLogoUrl")) === "/brand/header-logo.png"') expect(script).toContain('normalize(defaults.get("loginLogoUrl")) === "/brand/login-logo.png"') expect(script).toContain('!defaults.has("logoUrl") || normalize(defaults.get("logoUrl")) === "/brand/header-logo.png"') expect(script).toContain('SELECT id, \\"headerLogoUrl\\", \\"loginLogoUrl\\" FROM \\"system_settings\\" ORDER BY id') expect(script).toContain('parsed.hostname === "s3.jiansun.vip"') expect(script).toContain('parsed.pathname.startsWith("/gongxiangpan/")') expect(script).toContain('const rowsOk = brandRows.every') expect(script).toContain('!migrationOk || !rowsOk') expect(script).toContain('20260811_update_brand_logo_defaults') expect(script).toContain('20251118000000_add_upload_id_to_materials') expect(script).toContain('materials_uploadId_key') expect(script).toContain('column.data_type === "text"') expect(script).toContain('column.is_nullable === "YES"') expect(script).toContain('index.access_method === "btree"') expect(script).toContain('Number(index.indnkeyatts) === 1') expect(script).toContain('keyColumns.length === 1 && keyColumns[0] === "uploadId"') expect(script).toContain('normalizedPredicate === "uploadidisnotnullanddeletedatisnull"') expect(script).toContain('migrationByName.get(historicalMigrationName) === historicalSourceChecksum') expect(script).toContain('migrationByName.get(normalizationMigrationName) === normalizationSourceChecksum') expect(script).toContain('20260812_align_material_upload_idempotency_index') expect(script).toContain('20260813_add_material_custody_department') expect(script).toContain('20260813_add_organization_units_and_employment_state') expect(script).toContain('20260813_add_user_employee_binding') expect(dockerfile).toContain('/app/prisma/migrations/20251118000000_add_upload_id_to_materials/migration.sql') expect(dockerfile).toContain('COPY --from=builder --chown=root:root /app/prisma/migrations/20251118000000_add_upload_id_to_materials') expect(script).toContain('finished_at IS NOT NULL AND rolled_back_at IS NULL') expect(script).toContain('traffic was not switched') }) test('候选容器带预置 drain 启动,全部门禁和稳定切流后才恢复 worker', () => { const preDrain = script.indexOf(': > "$(transcode_drain_file "$NEXT_CONTAINER")"') const preTaskDetailDrain = script.indexOf(': > "$(task_detail_drain_file "$NEXT_CONTAINER")"', preDrain) const createCandidate = script.indexOf('create_container "$NEXT_CONTAINER" "$REGISTRY_COMMIT"') const inspectCandidate = script.indexOf('verify_container_uploads_mount_metadata "$NEXT_CONTAINER"', createCandidate) const startCandidate = script.indexOf('docker start "$NEXT_CONTAINER"', inspectCandidate) const health = script.indexOf('wait_for_container_health "$NEXT_CONTAINER"', startCandidate) const lease = script.indexOf('verify_transcode_lease_schema "$NEXT_CONTAINER"', health) const taskDetailLease = script.indexOf('verify_task_detail_upload_lease_schema "$NEXT_CONTAINER"', lease) const brand = script.indexOf('verify_brand_logo_defaults "$NEXT_CONTAINER"', taskDetailLease) const uploadIdempotency = script.indexOf('verify_material_upload_idempotency_schema "$NEXT_CONTAINER"', brand) const capability = script.indexOf('verify_video_transcode_capability "$NEXT_CONTAINER"', uploadIdempotency) const taskDetailCapability = script.indexOf('verify_task_detail_upload_capability "$NEXT_CONTAINER"', capability) const taskDetailInactive = script.indexOf('wait_for_task_detail_upload_drain "$NEXT_CONTAINER" 2', taskDetailCapability) const proxyConfig = script.indexOf('write_proxy_config "${NEXT_IP}:3000" "$NEXT_CONTAINER"', taskDetailInactive) const resumeCandidate = script.indexOf('resume_video_transcode "$NEXT_CONTAINER"', proxyConfig) const resumeTaskDetail = script.indexOf('resume_task_detail_upload "$NEXT_CONTAINER"', resumeCandidate) const workerCommit = script.indexOf('BACKGROUND_WORKERS_COMMITTED=1', resumeTaskDetail) const oldStop = script.indexOf('docker stop --time 30 "$CURRENT_CONTAINER"', workerCommit) expect(preDrain).toBeGreaterThan(0) expect(preTaskDetailDrain).toBeGreaterThan(preDrain) expect(createCandidate).toBeGreaterThan(preTaskDetailDrain) expect(inspectCandidate).toBeGreaterThan(createCandidate) expect(startCandidate).toBeGreaterThan(inspectCandidate) expect(health).toBeGreaterThan(startCandidate) expect(lease).toBeGreaterThan(health) expect(taskDetailLease).toBeGreaterThan(lease) expect(brand).toBeGreaterThan(taskDetailLease) expect(uploadIdempotency).toBeGreaterThan(brand) expect(capability).toBeGreaterThan(uploadIdempotency) expect(taskDetailCapability).toBeGreaterThan(capability) expect(taskDetailInactive).toBeGreaterThan(taskDetailCapability) expect(proxyConfig).toBeGreaterThan(taskDetailInactive) expect(resumeCandidate).toBeGreaterThan(proxyConfig) expect(resumeTaskDetail).toBeGreaterThan(resumeCandidate) expect(workerCommit).toBeGreaterThan(resumeTaskDetail) expect(oldStop).toBeGreaterThan(workerCommit) expect(script).toContain('remove_video_container_safely "$NEXT_CONTAINER"') expect(script).not.toContain('docker rm -f "$NEXT_CONTAINER"') }) test('worker 提交后任何晚期失败都不会解除旧槽 drain', () => { const trap = script.slice( script.indexOf('resume_transcode_after_failed_deploy() {'), script.indexOf('trap resume_transcode_after_failed_deploy EXIT'), ) expect(trap).toContain('[ "$BACKGROUND_WORKERS_COMMITTED" != "1" ]') expect(trap).toContain('for target in $TRANSCODE_DRAIN_TARGETS') expect(trap).toContain('for target in $TASK_DETAIL_DRAIN_TARGETS') expect(trap).toContain('previous background-worker drain markers remain enabled') expect(script.indexOf('BACKGROUND_WORKERS_COMMITTED=1')).toBeLessThan( script.indexOf('systemctl start "$PROXY_RECONCILE_SERVICE"'), ) const switchBlock = script.indexOf('SWITCH_REPORTED_FAILURE=0') const trafficMaySwitch = script.indexOf('BACKGROUND_WORKERS_COMMITTED=1', switchBlock) const switchTraffic = script.indexOf('if ! switch_proxy; then', trafficMaySwitch) expect(trafficMaySwitch).toBeGreaterThan(switchBlock) expect(switchTraffic).toBeGreaterThan(trafficMaySwitch) const abortStart = script.indexOf('abort_after_uncertain_switch() {') const abortEnd = script.indexOf('# The historical shadow topology', abortStart) const abort = script.slice(abortStart, abortEnd) expect(abort).toContain('if [ "${PROXY_LOCK_HELD:-0}" != "1" ]') expect(abort).toContain('flock -w 30 8') expect(script).toContain('abort_after_uncertain_switch "Proxy reconciler failed its post-deployment verification"') }) test('普通发布与首次 bootstrap 共用全局锁并只消费当前未消费 handoff', () => { const lock = script.indexOf('flock -w 30 9') const handoffGate = script.indexOf('verify_bootstrap_handoff_if_present') const candidate = script.indexOf('create_container "$NEXT_CONTAINER" "$REGISTRY_COMMIT"', handoffGate) const consume = script.indexOf('mv -- "$READY_BOOTSTRAP_HANDOFF/handoff.ready" "$READY_BOOTSTRAP_HANDOFF/consumed"', candidate) expect(lock).toBeGreaterThan(0) expect(handoffGate).toBeGreaterThan(lock) expect(candidate).toBeGreaterThan(handoffGate) expect(consume).toBeGreaterThan(candidate) expect(script).toContain('[ ! -e "$handoff/consumed" ] || continue') expect(script).toContain('The maintenance entry requires exactly one unconsumed bootstrap handoff') expect(script).toContain('A normal blue-green entry cannot consume a stale bootstrap handoff') expect(script).toContain('verify_ready_bootstrap_handoff_evidence "$READY_BOOTSTRAP_HANDOFF"') expect(script).toContain('maintenance_entry_is_ready_after_restore || fail "Bootstrap maintenance entry is not an exact dual-stack 503 handoff"') expect(script).toContain('Preserved legacy container is not the exact stopped handoff identity') expect(script).toContain('uploads_container_mount_metadata_is_trusted "$UPLOADS_MOUNT" "$mount_metadata"') }) test('maintenance 缺 handoff 会拒绝,已消费的旧 SHA 不阻塞后续蓝绿发布', async () => { const bash = findBash() if (!bash) return const root = await mkdtemp(path.join(os.tmpdir(), 'deploy-handoff-test-')) const attempt = path.join(root, 'attempt-1') const exactSha = 'a'.repeat(40) const nextSha = 'b'.repeat(40) const legacyId = '1'.repeat(64) const imageId = `sha256:${'2'.repeat(64)}` const repoDigest = `registry.example/app@sha256:${'3'.repeat(64)}` const preserved = `youyidian-3d-material-legacy-${exactSha.slice(0, 12)}-${legacyId.slice(0, 12)}` const uploadsSignature = 'abcd:1234' const support = ` fail() { printf '%s\\n' "$*" >&2; exit 91; } runtime_proxy_container() { printf '%s\\n' "\${MOCK_RUNTIME_TARGET:-}"; } is_proxy_running() { [ "\${MOCK_PROXY_RUNNING:-0}" = "1" ]; } maintenance_entry_is_ready_after_restore() { [ "\${MOCK_MAINTENANCE_READY:-0}" = "1" ]; } stat() { if [ "$1" = "-c" ] && [ "$2" = "%a" ]; then if [ "$3" = "\${MOCK_HANDOFF_DIR}" ]; then printf '700\\n'; else printf '600\\n'; fi return 0 fi command stat "$@" } docker() { if [ "$1" = "inspect" ] && [ "$2" = "--format" ]; then case "$3" in *Mounts*) printf '/app/uploads\\tbind\\t%s\\ttrue\\n' "\${MOCK_MOUNT_SOURCE}" ;; *) printf '%s\\t%s\\t%s\\t%s\\t%s\\n' "\${MOCK_CONTAINER_ID}" "\${MOCK_IMAGE_ID}" "\${MOCK_STATUS}" "\${MOCK_RUNNING}" "\${MOCK_PAUSED}" ;; esac return 0 fi if [ "$1" = "image" ] && [ "$2" = "inspect" ]; then printf '%s\\n' "\${MOCK_REPO_DIGEST}" return 0 fi if [ "$1" = "port" ]; then printf '0.0.0.0:%s\\n[::]:%s\\n' "\${PORT}" "\${PORT}" return 0 fi return 97 } ` const functions = [ support, extractBashFunction(script, 'container_uploads_mount_metadata'), extractBashFunction(script, 'uploads_container_mount_metadata_is_trusted'), extractBashFunction(script, 'verify_ready_bootstrap_handoff_evidence'), extractBashFunction(script, 'verify_bootstrap_handoff_if_present'), ].join('\n') const baseVariables = { TARGET: 'production', BOOTSTRAP_ROOT: toBashPath(root), COMMIT_SHA: exactSha, READY_BOOTSTRAP_HANDOFF: '', MOCK_PROXY_RUNNING: '1', MOCK_RUNTIME_TARGET: '', MOCK_MAINTENANCE_READY: '1', MOCK_CONTAINER_ID: legacyId, MOCK_IMAGE_ID: imageId, MOCK_STATUS: 'exited', MOCK_RUNNING: 'false', MOCK_PAUSED: 'false', MOCK_REPO_DIGEST: repoDigest, MOCK_MOUNT_SOURCE: '/mnt/PoolOne/youyidian-uploads', CONTAINER_NAME: 'youyidian-3d-material', HOST_UPLOADS_STATFS_SIGNATURE: uploadsSignature, UPLOADS_MOUNT: '/mnt/PoolOne/youyidian-uploads', PORT: '4320', MOCK_HANDOFF_DIR: toBashPath(attempt), } try { const missing = runBashGate(bash, functions, 'verify_bootstrap_handoff_if_present', { ...baseVariables, BOOTSTRAP_ROOT: toBashPath(path.join(root, 'missing')), }) expect(missing.status).not.toBe(0) await mkdir(attempt, { recursive: true }) const evidence = { 'target-commit': exactSha, 'legacy-container-id': legacyId, 'legacy-image-id': imageId, 'legacy-image-repo-digest': repoDigest, 'legacy-upstream': '172.18.0.4:3000', 'uploads-statfs-signature': uploadsSignature, 'preserved-container': preserved, 'handoff.ready': 'ready', } for (const [name, value] of Object.entries(evidence)) { await writeFile(path.join(attempt, name), `${value}\n`, 'utf8') } const ready = runBashGate(bash, functions, 'verify_bootstrap_handoff_if_present; printf "%s" "$READY_BOOTSTRAP_HANDOFF"', { ...baseVariables, }) expect(ready.status).toBe(0) expect(ready.stdout).toContain('attempt-1') for (const overrides of [ { MOCK_MAINTENANCE_READY: '0' }, { MOCK_STATUS: 'running', MOCK_RUNNING: 'true' }, { MOCK_PAUSED: 'true' }, { MOCK_CONTAINER_ID: '4'.repeat(64) }, { MOCK_IMAGE_ID: `sha256:${'5'.repeat(64)}` }, { MOCK_REPO_DIGEST: `registry.example/app@sha256:${'6'.repeat(64)}` }, { MOCK_MOUNT_SOURCE: '/wrong/uploads' }, ]) { const rejected = runBashGate(bash, functions, 'verify_bootstrap_handoff_if_present', { ...baseVariables, ...overrides, }) expect(rejected.status).not.toBe(0) } await rm(path.join(attempt, 'legacy-upstream')) const incomplete = runBashGate(bash, functions, 'verify_bootstrap_handoff_if_present', baseVariables) expect(incomplete.status).not.toBe(0) await writeFile(path.join(attempt, 'legacy-upstream'), '172.18.0.4:3000\n', 'utf8') await rm(path.join(attempt, 'handoff.ready')) await writeFile(path.join(attempt, 'consumed'), 'ready\n', 'utf8') const later = runBashGate(bash, functions, 'verify_bootstrap_handoff_if_present; printf "%s" "$READY_BOOTSTRAP_HANDOFF"', { ...baseVariables, COMMIT_SHA: nextSha, MOCK_RUNTIME_TARGET: 'youyidian-3d-material-blue', }) expect(later.status).toBe(0) expect(later.stdout).toBe('') } finally { await rm(root, { recursive: true, force: true }) } }, 30_000) test('迁移审计仅在显式迁移批次启用,并在候选创建前绑定数据库实际身份', () => { const audit = script.indexOf('verify_migration_audit_evidence') const optIn = script.indexOf('[switch]$RequireMigrationAudit') const rootParser = script.indexOf('--read-only --user 0:0 --cap-drop ALL --security-opt no-new-privileges', audit) const identity = script.indexOf('verify_candidate_database_identity "$NEXT_CONTAINER"', rootParser) const switchTraffic = script.indexOf('if ! switch_proxy; then', identity) expect(audit).toBeGreaterThan(0) expect(optIn).toBeGreaterThan(0) expect(rootParser).toBeGreaterThan(audit) expect(identity).toBeGreaterThan(rootParser) expect(switchTraffic).toBeGreaterThan(identity) expect(script).toContain('for await (const chunk of process.stdin)') expect(script).not.toContain('process.stdin.on("end", async () => {') }) test('只有显式迁移批次才校验审计来源提交与 Prisma 定义', () => { const gitSync = script.indexOf('COMMIT_SHA="$(git rev-parse HEAD)"') const ancestor = script.indexOf('git merge-base --is-ancestor "$MIGRATION_AUDIT_SOURCE_COMMIT" "$COMMIT_SHA"', gitSync) const schemaDiff = script.indexOf('git diff --quiet "$MIGRATION_AUDIT_SOURCE_COMMIT" "$COMMIT_SHA" -- prisma/schema.prisma prisma/migrations', ancestor) const build = script.indexOf('start_remote_step "Docker Build"', schemaDiff) expect(gitSync).toBeGreaterThan(0) expect(ancestor).toBeGreaterThan(gitSync) expect(schemaDiff).toBeGreaterThan(ancestor) expect(build).toBeGreaterThan(schemaDiff) expect(script).toContain('if [ "$REQUIRE_MIGRATION_AUDIT" = "1" ] && [ "$SKIP_DEPLOY" != "1" ]; then') expect(script).toContain('Database schema or migrations changed after the approved audit; create a fresh verified migration audit') }) test('日常应用发布默认不消费迁移审计,也不会因此进入迁移流程', () => { expect(script).toContain("'__REQUIRE_MIGRATION_AUDIT__'") expect(script).toContain('REQUIRE_MIGRATION_AUDIT=__REQUIRE_MIGRATION_AUDIT__') expect(script).toContain('if [ "$REQUIRE_MIGRATION_AUDIT" != "1" ]; then') expect(script).toContain('Migration audit is not required for this normal application release') expect(script).toContain('[ "$REQUIRE_MIGRATION_AUDIT" = "1" ] || return 0') }) test('迁移凭证仅在显式迁移批次绑定审计源提交', () => { expect(script).toContain('if ($RequireMigrationAudit -and [string]::IsNullOrWhiteSpace($MigrationAuditSourceCommit))') expect(script).toContain('-e MIGRATION_AUDIT_SOURCE_COMMIT="$MIGRATION_AUDIT_SOURCE_COMMIT"') expect(script).toContain('audit.sourceCommit !== process.env.MIGRATION_AUDIT_SOURCE_COMMIT') expect(script).not.toContain('audit.sourceCommit !== process.env.COMMIT_SHA') }) test('候选数据库身份 Node 门禁能消费有效 stdin,坏 JSON 必须失败', () => { const functionStart = script.indexOf('verify_candidate_database_identity() {') const nodeStartMarker = "node -e '\n" const nodeStart = script.indexOf(nodeStartMarker, functionStart) + nodeStartMarker.length const nodeEnd = script.indexOf("\n' < \"$MIGRATION_AUDIT_FILE\"", nodeStart) const nodeSource = script.slice(nodeStart, nodeEnd) const prismaMock = ` const Module = require('module'); const originalLoad = Module._load; Module._load = function(request, parent, isMain) { if (request === '@prisma/client') return { PrismaClient: class { async $queryRawUnsafe() { return [{ database: 'prod', user: 'app', server_address: '10.0.0.8', server_port: '5432' }]; } async $disconnect() {} } }; return originalLoad.call(this, request, parent, isMain); }; ` const audit = JSON.stringify({ database: { configuredHost: 'db.internal', configuredPort: '5432', configuredDatabase: 'prod', configuredUser: 'app', actualDatabase: 'prod', actualUser: 'app', serverAddress: '10.0.0.8', serverPort: '5432', } }) const env = { ...process.env, DATABASE_URL: 'postgresql://app:secret@db.internal:5432/prod' } const valid = spawnSync(process.execPath, ['-e', `${prismaMock}\n${nodeSource}`], { input: audit, encoding: 'utf8', env }) const invalid = spawnSync(process.execPath, ['-e', `${prismaMock}\n${nodeSource}`], { input: '{bad', encoding: 'utf8', env }) expect(functionStart).toBeGreaterThan(0) expect(nodeEnd).toBeGreaterThan(nodeStart) expect(valid.status).toBe(0) expect(invalid.status).not.toBe(0) }) test('Logo 解码 Node 门禁接受真实 PNG,损坏输入必须失败', () => { const functionStart = script.indexOf('decode_image_stream_with_candidate() {') const nodeStartMarker = "node -e '\n" const nodeStart = script.indexOf(nodeStartMarker, functionStart) + nodeStartMarker.length const nodeEnd = script.indexOf("\n'\n}", nodeStart) const nodeSource = script.slice(nodeStart, nodeEnd) const validPng = readFileSync(path.join(process.cwd(), 'public', 'brand', 'header-logo.png')) const valid = spawnSync(process.execPath, ['-e', nodeSource], { input: validPng, encoding: 'buffer' }) const invalid = spawnSync(process.execPath, ['-e', nodeSource], { input: Buffer.from('not-an-image'), encoding: 'buffer' }) expect(functionStart).toBeGreaterThan(0) expect(nodeEnd).toBeGreaterThan(nodeStart) expect(valid.status).toBe(0) expect(invalid.status).not.toBe(0) }) test('部署失败会恢复旧容器 drain,停止旧容器前再次核对 active 标记', () => { expect(script).toContain('trap resume_transcode_after_failed_deploy EXIT') expect(script).toContain('for target in $TRANSCODE_DRAIN_TARGETS') expect(script).toContain('resume_video_transcode "$target"') expect(script).toContain('for target in $TASK_DETAIL_DRAIN_TARGETS') expect(script).toContain('resume_task_detail_upload "$target"') const videoIdle = script.indexOf('wait_for_video_transcode_drain "$CURRENT_CONTAINER" 2 &&') const taskDetailIdle = script.indexOf('wait_for_task_detail_upload_drain "$CURRENT_CONTAINER" 2 &&', videoIdle) const stop = script.indexOf('docker stop --time 30 "$CURRENT_CONTAINER"', taskDetailIdle) expect(videoIdle).toBeGreaterThan(0) expect(taskDetailIdle).toBeGreaterThan(videoIdle) expect(stop).toBeGreaterThan(taskDetailIdle) }) test('stable proxy 缺失且存在 legacy direct 时,在任何容器或流量动作前 fail-closed', () => { const topologyGate = script.indexOf('# This general deployment entry must never perform the one-time direct-to-proxy') const inspectLegacy = script.indexOf('LEGACY_IMAGE="$(docker inspect', topologyGate) const checkProxy = script.indexOf('if is_proxy_running; then', inspectLegacy) const failClosed = script.indexOf('Use the controlled one-time direct-to-proxy migration SOP', checkProxy) const proxyMutation = script.indexOf('mkdir -p "$PROXY_ROOT"', failClosed) const drainExisting = script.indexOf('request_video_transcode_drain "$TRANSCODE_DRAIN_SOURCE"', proxyMutation) const removeInactiveSlot = script.indexOf('remove_video_container_safely "$NEXT_CONTAINER"', drainExisting) const startCandidate = script.indexOf('create_container "$NEXT_CONTAINER" "$REGISTRY_COMMIT"', removeInactiveSlot) const switchTraffic = script.indexOf('if ! switch_proxy; then', startCandidate) const stopPrevious = script.indexOf('docker stop --time 30 "$CURRENT_CONTAINER"', switchTraffic) expect(topologyGate).toBeGreaterThan(0) expect(inspectLegacy).toBeGreaterThan(topologyGate) expect(checkProxy).toBeGreaterThan(inspectLegacy) expect(failClosed).toBeGreaterThan(checkProxy) expect(proxyMutation).toBeGreaterThan(failClosed) expect(drainExisting).toBeGreaterThan(proxyMutation) expect(removeInactiveSlot).toBeGreaterThan(drainExisting) expect(startCandidate).toBeGreaterThan(removeInactiveSlot) expect(switchTraffic).toBeGreaterThan(startCandidate) expect(stopPrevious).toBeGreaterThan(switchTraffic) expect(script).toContain('no container or traffic was changed') }) test('通用脚本不包含 automatic direct→proxy migration 或 direct rollback', () => { const rollbackStart = script.indexOf('rollback_to_previous_entry() {') const rollbackEnd = script.indexOf('abort_after_uncertain_switch() {', rollbackStart) const rollback = script.slice(rollbackStart, rollbackEnd) const operationalStart = script.indexOf('# This general deployment entry must never perform the one-time direct-to-proxy') const operational = script.slice(operationalStart) expect(rollback).toContain('[ "$PROXY_WAS_RUNNING" = "1" ]') expect(rollback).not.toContain('[ "$PROXY_WAS_RUNNING" != "1" ]') expect(rollback).not.toContain('create_container "$CONTAINER_NAME"') expect(rollback).not.toContain('stop_proxy') expect(operational).not.toContain('migrating public entry from direct container to proxy') expect(operational).not.toContain('proxy migration was not attempted') }) })