测试库不再使用 SQLite 内存库,改为每个测试包独立的 MySQL 库, 表结构由 backend/migrations 的真实迁移建立。 基础设施 - 新增 internal/database/testdb.go:NewTestDB() 保持原签名(28 处调用零改动), 内部连接 MySQL 测试库,首次调用重建库并执行全部真实迁移,之后每次调用清空全表 - 库名 = 测试库基础名 + 包目录名,同包共享、跨包隔离,使 go test ./... 可并发执行 (共用一库时各包会互相 DROP/CREATE 导致 database exists) - 新增 NewDryRunDB():MySQL 方言 + DryRun,供断言 SQL 形态的 5 处测试使用 - 新增 AutoMigrateIfMissing():只对不存在的表 AutoMigrate, 避免改动迁移定义的索引与约束 - 新增 scripts/prepare-test-db.sh:建库与通配授权,可重复执行 - 33 处 sqlite.Open 全部替换,26 个测试文件改用 MySQL - 删除 46 处手写 SQLite 方言 DDL(AUTOINCREMENT / INTEGER PRIMARY KEY / TEXT), 表统一由真实迁移建立 - go.mod 移除 gorm.io/driver/sqlite 与 mattn/go-sqlite3 - 删除生产代码里 distribution/repository.go 的 sqlite 方言分支(strftime) 对齐环境后暴露并修复的真实问题 - DATE(col) 在 MySQL 返回 DATE 类型,使 Date string 字段变成 RFC3339 时间串, 大盘日期会带出时间部分;28 处改为 DATE_FORMAT(col, '%Y-%m-%d') - 系统自动创建分销结算批次时 created_by 写入 0,违反指向 admin_users 的外键, 生产会直接插入失败;改为写 NULL(新增 adminRef helper) - MySQL DATETIME 仅到秒,静默截断 Go 侧写入的时间, 导致租用时长计算与结算时点回读比对偏差; rental_orders 的 9 个业务时间列提到 DATETIME(6) - 测试数据补齐严格模式要求的 NOT NULL 列、修正超出列长的取值、补齐外键父记录 迁移 - 000065 的 ALTER TABLE rental_orders 一并完成业务时间列精度调整, feature 侧不再单开迁移文件 脚本与文档 - check.sh / check-backend-coverage.sh 去掉 -p 1 串行与 SQLite 相关注释 - 更新 docs/Repository层测试补充总结.md 的测试运行说明
146 lines
4.5 KiB
Bash
Executable File
146 lines
4.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# 全量代码门禁:部署前在本地开发机执行,确保后端 + 前端检查全绿。
|
||
#
|
||
# 用途:
|
||
# - 部署门禁:scripts/deploy-prod.sh 在 build 镜像前自动调用本脚本(--skip-check 可跳过)
|
||
# - 手动校验:开发时手动跑 ./scripts/check.sh
|
||
#
|
||
# 包含:
|
||
# 数据库:SQL 约束与迁移完整性检查
|
||
# 后端:go vet + go build + go test
|
||
# 前端:npm run check (prettier + eslint + typecheck) + npm run test + npm run build
|
||
#
|
||
# 任一步骤失败即退出非零,阻止部署。
|
||
set -euo pipefail
|
||
|
||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||
BACKEND_DIR="${ROOT_DIR}/backend"
|
||
FRONTEND_DIR="${ROOT_DIR}/frontend"
|
||
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[1;34m'
|
||
NC='\033[0m'
|
||
|
||
log() { printf "${BLUE}[check]${NC} %s\n" "$*"; }
|
||
log_ok() { printf "${GREEN}[check]${NC} %s\n" "$*"; }
|
||
log_warn() { printf "${YELLOW}[check]${NC} %s\n" "$*"; }
|
||
log_error() { printf "${RED}[check]${NC} %s\n" "$*" >&2; }
|
||
|
||
SKIP_BACKEND=0
|
||
SKIP_FRONTEND=0
|
||
SKIP_TESTS=0
|
||
|
||
show_help() {
|
||
cat << EOF
|
||
用法: ./scripts/check.sh [选项]
|
||
|
||
全量代码门禁:后端 go vet/build/test + 前端 check/test/build。
|
||
|
||
选项:
|
||
--skip-backend 跳过后端检查
|
||
--skip-frontend 跳过前端检查
|
||
--skip-tests 跳过测试(仅做 vet/build + lint/typecheck/build)
|
||
-h, --help 显示帮助
|
||
|
||
示例:
|
||
./scripts/check.sh
|
||
./scripts/check.sh --skip-frontend
|
||
EOF
|
||
}
|
||
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
--skip-backend) SKIP_BACKEND=1; shift ;;
|
||
--skip-frontend) SKIP_FRONTEND=1; shift ;;
|
||
--skip-tests) SKIP_TESTS=1; shift ;;
|
||
-h|--help) show_help; exit 0 ;;
|
||
*)
|
||
log_error "未知参数: $1"
|
||
show_help
|
||
exit 1
|
||
;;
|
||
esac
|
||
done
|
||
|
||
step_start() { printf "\n${BLUE}[check]${NC} \033[1m▶ %s${NC}\n" "$1"; }
|
||
step_ok() { log_ok "✓ $1 ($2)"; }
|
||
step_fail() { log_error "✗ $1 失败"; }
|
||
|
||
run_step() {
|
||
# run_step "步骤名" 命令...
|
||
local name="$1"; shift
|
||
local start
|
||
start=$(date +%s)
|
||
step_start "$name"
|
||
if "$@"; then
|
||
local elapsed=$(( $(date +%s) - start ))
|
||
step_ok "$name" "${elapsed}s"
|
||
else
|
||
step_fail "$name"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
FAILED=0
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 后端检查
|
||
# ---------------------------------------------------------------------------
|
||
if [[ "${SKIP_BACKEND}" == "0" ]]; then
|
||
log "后端检查目录: ${BACKEND_DIR}"
|
||
cd "${BACKEND_DIR}"
|
||
|
||
run_step "SQL 约束检查" bash "${ROOT_DIR}/scripts/check-sql-guard.sh" || FAILED=1
|
||
run_step "go vet ./..." go vet ./... || FAILED=1
|
||
run_step "go build ./..." go build ./... || FAILED=1
|
||
if [[ "${SKIP_TESTS}" == "0" ]]; then
|
||
# 后端测试跑在真实 MySQL 上:每个包使用独立测试库 hfb_sys_test_<包名>,可并发执行。
|
||
# 真实链路 e2e(internal/e2e 中依赖 HFB_E2E_DSN 的用例)需要显式配置该变量。
|
||
run_step "准备 MySQL 测试库" bash "${ROOT_DIR}/scripts/prepare-test-db.sh" || FAILED=1
|
||
run_step "go test + coverage" bash "${ROOT_DIR}/scripts/check-backend-coverage.sh" || FAILED=1
|
||
else
|
||
log_warn "跳过后端测试 (--skip-tests)"
|
||
fi
|
||
else
|
||
log_warn "跳过后端检查 (--skip-backend)"
|
||
fi
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 前端检查
|
||
# ---------------------------------------------------------------------------
|
||
if [[ "${SKIP_FRONTEND}" == "0" ]]; then
|
||
log "前端检查目录: ${FRONTEND_DIR}"
|
||
cd "${FRONTEND_DIR}"
|
||
|
||
run_step "Vue 页面大小门禁" bash "${ROOT_DIR}/scripts/check-vue-size.sh" || FAILED=1
|
||
|
||
if [[ ! -d node_modules ]]; then
|
||
run_step "npm ci (首次安装依赖)" npm ci || FAILED=1
|
||
fi
|
||
|
||
# npm run check = prettier --check + eslint --max-warnings=0 + vue-tsc
|
||
run_step "npm run check (prettier + eslint + typecheck)" npm run check || FAILED=1
|
||
if [[ "${SKIP_TESTS}" == "0" ]]; then
|
||
run_step "npm run test + coverage (vitest)" npm run test:coverage || FAILED=1
|
||
else
|
||
log_warn "跳过前端测试 (--skip-tests)"
|
||
fi
|
||
run_step "npm run build (vite 生产构建)" npm run build || FAILED=1
|
||
else
|
||
log_warn "跳过前端检查 (--skip-frontend)"
|
||
fi
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 汇总
|
||
# ---------------------------------------------------------------------------
|
||
printf "\n"
|
||
if [[ "${FAILED}" == "0" ]]; then
|
||
log_ok "全部检查通过 ✓"
|
||
exit 0
|
||
else
|
||
log_error "检查未通过,已阻止本次部署。可用 --skip-check 强制跳过(不推荐)。"
|
||
exit 1
|
||
fi
|