gongxiangp2/lib/__tests__/video-transcode-deploy-drain.test.ts

1125 lines
62 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/** @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<string, string>,
) {
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 </tmp/git-deploy-public-logo-urls.txt', configuredLogoValidation)
const fallbackLogos = script.indexOf('for fallback_logo in header-logo.png login-logo.png', configuredLogoFetch)
expect(publicProbe).toBeGreaterThan(0)
expect(committedTarget).toBeGreaterThan(publicProbe)
expect(payloadGate).toBeGreaterThan(committedTarget)
expect(targetGate).toBeGreaterThan(payloadGate)
expect(s3Gate).toBeGreaterThan(targetGate)
expect(settings).toBeGreaterThan(s3Gate)
expect(configuredLogoValidation).toBeGreaterThan(settings)
expect(configuredLogoFetch).toBeGreaterThan(configuredLogoValidation)
expect(fallbackLogos).toBeGreaterThan(configuredLogoFetch)
expect(script).toContain('Public application endpoint does not prove the expected commit and healthy database/Redis')
expect(script).toContain('abort_after_uncertain_switch "Public S3 endpoint is unhealthy after cutover"')
expect(script).toContain('did not return an image Content-Type')
expect(script).not.toContain('[ "$public_target" = "$NEXT_CONTAINER" ]')
expect(script).not.toContain('Public S3 endpoint is unhealthy; application deployment succeeded')
})
test('候选必须在切流前验证数据库身份、S3 认证及全部品牌图解码', () => {
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')
})
})