文件
hyb-oa/scripts/qa/run-isolated-regression-dedup.sh
T

267 行
7.5 KiB
Bash
可执行文件

#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
SERVER_DIR="$ROOT_DIR/server"
QA_HOST="${QA_HOST:-127.0.0.1}"
QA_PORT_INPUT="${QA_PORT:-}"
QA_PORT="${QA_PORT_INPUT:-3000}"
QA_READY_TIMEOUT_SECONDS="${QA_READY_TIMEOUT_SECONDS:-90}"
QA_DB_CLIENT="${QA_DB_CLIENT:-host}"
QA_DB_HOST="${QA_DB_HOST:-127.0.0.1}"
QA_DB_PORT="${QA_DB_PORT:-5432}"
QA_DB_USER="${QA_DB_USER:-hyboa_user}"
QA_DB_PASSWORD="${QA_DB_PASSWORD:-hyboa_password}"
QA_DB_ADMIN_DB="${QA_DB_ADMIN_DB:-postgres}"
QA_DB_DOCKER_CONTAINER="${QA_DB_DOCKER_CONTAINER:-hyb-oa-trialrun-db}"
QA_DOCKER_USE_SG="${QA_DOCKER_USE_SG:-0}"
QA_DB_PREFIX="${QA_DB_PREFIX:-hyb_oa_dedup}"
QA_DB_NAME="${QA_DB_NAME:-${QA_DB_PREFIX}_$(date +%Y%m%d%H%M%S)_$RANDOM}"
QA_KEEP_DB="${QA_KEEP_DB:-0}"
QA_ARTIFACT_DIR="${QA_ARTIFACT_DIR:-$ROOT_DIR/artifacts/qa-flow/isolated-dedup-$(date +%Y%m%d-%H%M%S)}"
QA_SERVER_LOG="$QA_ARTIFACT_DIR/server.log"
QA_SERVER_PID_FILE="$QA_ARTIFACT_DIR/server.pid"
QA_BASE_URL=""
TEST_FIELD_ENCRYPTION_KEY_DEFAULT="${FIELD_ENCRYPTION_KEY:-MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=}"
mkdir -p "$QA_ARTIFACT_DIR"
require_cmd() {
command -v "$1" >/dev/null 2>&1 || {
echo "[ERROR] 缺少命令: $1" >&2
exit 1
}
}
docker_cmd() {
if [[ "$QA_DOCKER_USE_SG" == "1" ]]; then
local rendered="docker"
local quoted
for arg in "$@"; do
printf -v quoted "%q" "$arg"
rendered+=" $quoted"
done
sg docker -c "$rendered"
return
fi
docker "$@"
}
require_db_client() {
case "$QA_DB_CLIENT" in
host)
require_cmd psql
require_cmd createdb
require_cmd dropdb
;;
docker)
require_cmd docker
if [[ "$QA_DOCKER_USE_SG" == "1" ]]; then
require_cmd sg
fi
if ! docker_cmd inspect "$QA_DB_DOCKER_CONTAINER" >/dev/null 2>&1; then
echo "[ERROR] PostgreSQL 容器不可用: $QA_DB_DOCKER_CONTAINER" >&2
exit 1
fi
;;
*)
echo "[ERROR] QA_DB_CLIENT 仅支持 host 或 docker,当前: $QA_DB_CLIENT" >&2
exit 1
;;
esac
}
port_in_use() {
python3 - "$QA_HOST" "$QA_PORT" <<'PY'
import socket, sys
host = sys.argv[1]
port = int(sys.argv[2])
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.settimeout(0.5)
try:
sock.connect((host, port))
except OSError:
sys.exit(1)
sys.exit(0)
PY
}
find_available_port() {
python3 - "$QA_HOST" "$QA_PORT" <<'PY'
import socket, sys
host = sys.argv[1]
start = int(sys.argv[2])
for port in range(start, start + 200):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
sock.bind((host, port))
except OSError:
continue
print(port)
sys.exit(0)
raise SystemExit(1)
PY
}
select_port() {
if port_in_use; then
if [[ -n "$QA_PORT_INPUT" ]]; then
echo "[ERROR] 端口 ${QA_PORT} 已被占用,请改用 QA_PORT=<port> 后重试。" >&2
exit 1
fi
QA_PORT="$(find_available_port)"
echo "[INFO] 默认端口 3000 已被占用,自动切换到 ${QA_PORT}"
fi
QA_BASE_URL="http://${QA_HOST}:${QA_PORT}"
}
psql_admin() {
if [[ "$QA_DB_CLIENT" == "docker" ]]; then
docker_cmd exec -e "PGPASSWORD=$QA_DB_PASSWORD" "$QA_DB_DOCKER_CONTAINER" \
psql -v ON_ERROR_STOP=1 -U "$QA_DB_USER" -d "$QA_DB_ADMIN_DB" "$@"
return
fi
PGPASSWORD="$QA_DB_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$QA_DB_HOST" -p "$QA_DB_PORT" -U "$QA_DB_USER" -d "$QA_DB_ADMIN_DB" "$@"
}
create_db() {
if [[ "$QA_DB_CLIENT" == "docker" ]]; then
docker_cmd exec -e "PGPASSWORD=$QA_DB_PASSWORD" "$QA_DB_DOCKER_CONTAINER" \
createdb -U "$QA_DB_USER" "$QA_DB_NAME"
return
fi
PGPASSWORD="$QA_DB_PASSWORD" createdb -h "$QA_DB_HOST" -p "$QA_DB_PORT" -U "$QA_DB_USER" "$QA_DB_NAME"
}
drop_db() {
if [[ "$QA_KEEP_DB" == "1" ]]; then
echo "[INFO] QA_KEEP_DB=1,保留临时数据库:$QA_DB_NAME"
return
fi
psql_admin -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '${QA_DB_NAME}' AND pid <> pg_backend_pid();" >/dev/null || true
if [[ "$QA_DB_CLIENT" == "docker" ]]; then
docker_cmd exec -e "PGPASSWORD=$QA_DB_PASSWORD" "$QA_DB_DOCKER_CONTAINER" \
dropdb --if-exists -U "$QA_DB_USER" "$QA_DB_NAME" >/dev/null || true
return
fi
PGPASSWORD="$QA_DB_PASSWORD" dropdb --if-exists -h "$QA_DB_HOST" -p "$QA_DB_PORT" -U "$QA_DB_USER" "$QA_DB_NAME" >/dev/null || true
}
stop_server() {
if [[ -f "$QA_SERVER_PID_FILE" ]]; then
local pid
pid="$(cat "$QA_SERVER_PID_FILE")"
if [[ -n "$pid" ]] && kill -0 "$pid" >/dev/null 2>&1; then
kill "$pid" >/dev/null 2>&1 || true
for _ in $(seq 1 15); do
if ! kill -0 "$pid" >/dev/null 2>&1; then
break
fi
sleep 1
done
kill -0 "$pid" >/dev/null 2>&1 && kill -9 "$pid" >/dev/null 2>&1 || true
wait "$pid" >/dev/null 2>&1 || true
fi
rm -f "$QA_SERVER_PID_FILE"
fi
}
cleanup() {
stop_server
drop_db
}
trap cleanup EXIT
wait_ready() {
local elapsed=0
while [[ "$elapsed" -lt "$QA_READY_TIMEOUT_SECONDS" ]]; do
if curl -fsS "$QA_BASE_URL/ready" >/dev/null 2>&1; then
return 0
fi
sleep 2
elapsed=$((elapsed + 2))
done
echo "[ERROR] /ready 在 ${QA_READY_TIMEOUT_SECONDS}s 内未通过:$QA_BASE_URL/ready" >&2
echo "[ERROR] server log: $QA_SERVER_LOG" >&2
return 1
}
export_app_env() {
export NODE_ENV=test
export HOST="$QA_HOST"
export PORT="$QA_PORT"
export APP_ORIGIN="$QA_BASE_URL"
export BASE_URL="$QA_BASE_URL"
export DATABASE_URL="postgresql://${QA_DB_USER}:${QA_DB_PASSWORD}@${QA_DB_HOST}:${QA_DB_PORT}/${QA_DB_NAME}?schema=public"
export JWT_SECRET="${JWT_SECRET:-qa_isolated_dedup_secret_key}"
export FIELD_ENCRYPTION_KEY="$TEST_FIELD_ENCRYPTION_KEY_DEFAULT"
export JOBS_ENABLED=false
export OCR_PROVIDER="${OCR_PROVIDER:-mock}"
export OCR_MAX_ATTEMPTS="${OCR_MAX_ATTEMPTS:-3}"
export METRICS_ENABLED="${METRICS_ENABLED:-false}"
export RATE_LIMIT_ENABLED="${RATE_LIMIT_ENABLED:-false}"
export PORTAL_HOME_ENABLED=true
export GLOBAL_SEARCH_ENABLED=true
export ANNOUNCEMENTS_ENABLED=true
export PRESENCE_BOARD_ENABLED=true
export ATTENDANCE_ENABLED=true
export WEEKLY_REPORT_ENABLED=true
export EMPLOYEE_LIFECYCLE_ENABLED=true
export TODO_BOARD_ENABLED=true
export PROJECT_DOMAIN_ENABLED=true
export PROJECT_LINKING_ENABLED=true
export PROJECT_COMMUNICATION_LOG_ENABLED=true
export PROJECT_CLOSURE_CHECKLIST_ENABLED=true
export CONFIDENTIAL_DOC_REFS_ENABLED=true
export TRAVEL_SYNC_ENABLED=true
}
require_cmd node
require_cmd npm
require_cmd curl
require_cmd jq
require_cmd python3
require_db_client
select_port
export_app_env
echo ">>> [isolated-dedup] 创建临时数据库:$QA_DB_NAME"
create_db
echo ">>> [isolated-dedup] Prisma generate + migrate deploy"
(
cd "$SERVER_DIR"
npm run prisma:generate
npm run prisma:migrate:deploy
)
echo ">>> [isolated-dedup] 写入开发种子数据"
(
cd "$SERVER_DIR"
SEED_DEV_ALLOW=1 SEED_DEV_DATABASE_CONFIRM="$QA_DB_NAME" npm run seed:dev
)
echo ">>> [isolated-dedup] 启动临时服务:$QA_BASE_URL"
(
cd "$SERVER_DIR"
npm run dev >"$QA_SERVER_LOG" 2>&1 &
echo "$!" >"$QA_SERVER_PID_FILE"
)
wait_ready
echo ">>> [isolated-dedup] 执行真实 HTTP 发票查重回归"
(
cd "$SERVER_DIR"
BASE_URL="$QA_BASE_URL" DATABASE_URL="$DATABASE_URL" ./scripts/regression-dedup.sh
)
echo "[isolated-dedup][PASS] regression-dedup 通过"
echo "[isolated-dedup] 产物目录:$QA_ARTIFACT_DIR"