flnxi-Wu/scripts/__tests__/deploy-registry-script.test.ts
2026-06-02 19:25:48 +08:00

134 lines
7.4 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.

import fs from 'fs'
import path from 'path'
describe('deploy-registry.ps1', () => {
const script = fs.readFileSync(
path.join(process.cwd(), 'deploy-registry.ps1'),
'utf8'
)
it('远程部署默认包含升级前数据库、对象存储和 .env 备份', () => {
expect(script).toContain('[switch]$NoRemoteDbBackup')
expect(script).toContain('[switch]$NoRemoteObjectStorageBackup')
expect(script).toContain('[switch]$ExternalDataBackupConfirmed')
expect(script).toContain('[string]$RemoteBackupHelperImage = "busybox:1.36"')
expect(script).toContain('env_backup="$backup_dir/.env.before-upgrade"')
expect(script).toContain('enter_maintenance_for_backup')
expect(script).toContain('APP_STOPPED_FOR_BACKUP.txt')
expect(script).toContain("docker compose -f \"$compose_file\" exec -T postgres sh -c 'pg_dump")
expect(script).toContain('test -s "$db_backup"')
expect(script).toContain('backup_minio_volume()')
expect(script).toContain('docker volume inspect "$minio_volume_name"')
expect(script).toContain('tar -czf /backup/minio-data.tar.gz')
expect(script).toContain('rm -rf /target/* /target/.[!.]* /target/..?*')
expect(script).toContain('OBJECT_STORAGE_EXTERNAL.txt')
expect(script).toContain('backup_minio_volume')
expect(script).toContain('is_external_database()')
expect(script).toContain('is_external_object_storage()')
expect(script).toContain('EXTERNAL_DATABASE_BACKUP_REQUIRED.txt')
expect(script).toContain('EXTERNAL_OBJECT_STORAGE_BACKUP_REQUIRED.txt')
expect(script).toContain('external_data_backup_confirmed=$externalDataBackupConfirmed')
expect(script).toContain('外部数据库备份未确认,已中止升级')
expect(script).toContain('外部对象存储备份未确认,已中止升级')
})
it('远程部署失败时默认会恢复旧 APP_IMAGE 或旧容器镜像', () => {
expect(script).toContain('[switch]$NoRollback')
expect(script).toContain('rollback()')
expect(script).toContain('trap rollback ERR')
expect(script.indexOf('trap rollback ERR')).toBeLessThan(script.indexOf('upsert_env APP_IMAGE'))
expect(script).toContain('write_deploy_context')
expect(script).toContain('LAST_DEPLOY_CONTEXT.env')
expect(script).toContain('rollback_image="$old_container_image"')
expect(script).toContain('docker compose -f "$compose_file" up -d --no-deps --pull never --force-recreate "$compose_service" || true')
expect(script).toContain('自动回滚只恢复应用镜像和 .env,不自动恢复数据库')
})
it('本地最终健康检查失败时会触发远程回滚', () => {
expect(script).toContain('function New-RemoteRollbackScript')
expect(script).toContain('应用健康检查失败,尝试远程回滚')
expect(script).toContain('New-RemoteRollbackScript -Reason $healthError')
expect(script).toContain('Invoke-RemoteCommand -Command $rollbackScript -NoRegistryPasswordInput')
expect(script).toContain('本地最终健康检查失败,准备远程回滚')
expect(script).toContain('cp "$env_backup" .env')
expect(script).toContain('docker compose -f "`${compose_file:-docker-compose.prod.yml}" up -d --no-deps --pull never --force-recreate "`${compose_service:-app}"')
})
it('部署后校验运行镜像、版本文件和数据库迁移状态', () => {
expect(script).toContain('verify_running_image')
expect(script).toContain('actual_image="$(docker inspect "$container_id" --format')
expect(script).toContain('运行镜像不匹配')
expect(script).toContain('运行版本不匹配')
expect(script).toContain('npx prisma migrate status --schema=prisma/schema.prisma')
})
it('远程健康检查使用可配置超时和最终访问入口', () => {
expect(script).toContain('[int]$RemoteHealthTimeoutSec = 120')
expect(script).toContain('$baseUrl = if (-not [string]::IsNullOrWhiteSpace($AppPublicUrl))')
expect(script).toContain('$deadline = (Get-Date).AddSeconds($RemoteHealthTimeoutSec)')
expect(script).toContain('健康检查没有在 $RemoteHealthTimeoutSec 秒内通过')
expect(script).not.toContain('健康检查没有在 60 秒内通过')
})
it('运行时镜像同步包含对象存储备份 helper', () => {
expect(script).toContain('Source = "busybox:1.36"')
expect(script).toContain('Repository = "runtime/busybox"')
expect(script).toContain('Tag = "1.36"')
})
it('支持发布付费模块镜像并输出验证服务器 digest 配置', () => {
expect(script).toContain('"PublishPremiumModule"')
expect(script).toContain('[string]$PremiumModuleId = "intranet-penetration-core"')
expect(script).toContain('[string]$PremiumModuleRepositoryPrefix = "premium"')
expect(script).toContain('[switch]$AllowMissingPremiumModuleDigest')
expect(script).toContain('function Invoke-PublishPremiumModule')
expect(script).toContain('$moduleRepository = "$($PremiumModuleRepositoryPrefix.Trim(\'/\'))/$moduleId"')
expect(script).toContain('if (-not $SkipPush)')
expect(script).toContain('未能自动读取付费模块镜像 digest')
expect(script).toContain('Get-PushedImageDigest -Image $registryImage')
expect(script).toContain('PREMIUM_MODULE_${envKey}_DIGEST=$digest')
})
it('基础镜像构建上下文必须排除付费模块源码', () => {
const dockerignore = fs.readFileSync(path.join(process.cwd(), '.dockerignore'), 'utf8')
expect(dockerignore).toContain('premium-modules')
expect(dockerignore).toContain('deploy/registry-proxy')
})
it('试跑和只构建付费模块时不应强制连接 Registry', () => {
expect(script).toContain('if ($DryRun) {\n return\n }')
expect(script).toContain('已跳过推送付费模块镜像;未推送时无法作为客户拉取源使用')
expect(script).toContain('$digest = if ($SkipPush) { "" } else { Get-PushedImageDigest -Image $registryImage }')
})
it('远端 shell 变量应以单引号字符串保留,避免被 PowerShell 提前展开', () => {
expect(script).toContain('$lines.Add(\'docker compose -f "$compose_file" up -d --pull never\')')
expect(script).toContain('$lines.Add(\'docker compose -f "$compose_file" ps\')')
expect(script).not.toContain('$lines.Add("docker compose -f `"$compose_file`"')
})
it('支持飞牛/NAS 普通管理员通过 sudo 执行远程 Docker 升级', () => {
expect(script).toContain('[switch]$RemoteSudo')
expect(script).toContain('[SecureString]$RemoteSudoPassword = $null')
expect(script).toContain('function Ensure-RemoteSudoPasswordIfNeeded')
expect(script).toContain('$script:RemoteSudoPassword = $Password')
expect(script).toContain('remote_sudo_enabled=$remoteSudoEnabled')
expect(script).toContain('IFS= read -r SUDO_PASSWORD')
expect(script).toContain('sudo -n docker "$@"')
expect(script).toContain('sudo -n docker "`$@"')
expect(script).toContain('Invoke-RemoteCommand -Command $rollbackScript -NoRegistryPasswordInput')
})
it('发送到 Linux 的远程脚本会统一去除 Windows 回车符', () => {
expect(script).toContain('function Normalize-RemoteShellScript')
expect(script).toContain('($Script -replace "`r`n", "`n") -replace "`r", ""')
expect(script).toContain('$normalizedCommand = Normalize-RemoteShellScript -Script $Command')
expect(script).toContain('ConvertTo-ShellSingleQuoted -Value $normalizedCommand')
})
it('读取远端 .env 时会去除 Windows 行尾回车', () => {
expect(script).toContain('gsub(/\\r$/, "", value)')
})
})