You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
144 lines
7.3 KiB
144 lines
7.3 KiB
#!/usr/bin/env bash
|
|
# S01 第四轮 F6 —— sms_verification_codes 表结构契约门禁
|
|
#
|
|
# 目的:防止冒烟脚本再次使用**不存在的列**(F6:expires_at),
|
|
# 且不允许测试自己维护一份与生产无关的假字段列表来自证正确。
|
|
#
|
|
# 契约来源(本脚本同时使用前两个,并交叉校验二者一致):
|
|
# 1. 实体 @Column:mydreamjob-server/.../auth/SmsVerificationCode.java
|
|
# 2. 建表 Flyway:mydreamjob-server/.../db/migration/V1__base_schema.sql
|
|
# 3. 真实 MySQL SHOW COLUMNS —— 属**真实验收阶段**,本脚本只输出对账命令,
|
|
# 不在自动化阶段伪造该来源。
|
|
#
|
|
# 重要:expires_at 是 personal_data_export_tasks(V30) / ability_attempts(V35) /
|
|
# talent_invitations(V40) 三张表的**真实列**。因此本门禁只对
|
|
# sms_verification_codes 这一个表断言"不存在 expires_at",
|
|
# 不得写成全库禁令(那会误报,并掩盖真正的字段错误)。
|
|
#
|
|
# 运行: bash docs/tests/smoke-test-schema-contract-test.sh
|
|
set -u
|
|
|
|
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
SMOKE="$REPO_ROOT/docs/smoke-test.sh"
|
|
ENTITY="$REPO_ROOT/mydreamjob-server/src/main/java/com/mydreamjob/auth/SmsVerificationCode.java"
|
|
MIGRATION_DIR="$REPO_ROOT/mydreamjob-server/src/main/resources/db/migration"
|
|
FLYWAY="$MIGRATION_DIR/V1__base_schema.sql"
|
|
TABLE="sms_verification_codes"
|
|
|
|
PASS=0
|
|
FAIL=0
|
|
ok() { echo "[PASS] $1"; PASS=$((PASS + 1)); }
|
|
bad() { echo "[FAIL] $1"; FAIL=$((FAIL + 1)); }
|
|
check() { if [ "$1" = "1" ]; then ok "$2"; else bad "$2"; fi; }
|
|
|
|
echo "=== S01 F6: sms_verification_codes 表结构契约门禁 ==="
|
|
echo "实体来源 : $ENTITY"
|
|
echo "Flyway来源: $FLYWAY"
|
|
echo "脚本 : $SMOKE"
|
|
echo ""
|
|
|
|
# ── 契约来源 1:实体 @Column ──────────────────────────────────
|
|
entity_cols=""
|
|
if [ -f "$ENTITY" ]; then
|
|
# 主键 id 在实体中以 @Id 标注(无 @Column(name=...)),单独补入
|
|
grep -qE '^[[:space:]]*@Id[[:space:]]*$' "$ENTITY" && entity_cols="id"
|
|
entity_cols="$entity_cols $(grep -oE '@Column\(name[[:space:]]*=[[:space:]]*"[A-Za-z0-9_]+"' "$ENTITY" \
|
|
| sed 's/.*"\(.*\)"/\1/')"
|
|
entity_cols="$(printf '%s' "$entity_cols" | tr ' ' '\n' | grep -v '^$' | sort -u | tr '\n' ' ')"
|
|
fi
|
|
|
|
# ── 契约来源 2:建表 Flyway ───────────────────────────────────
|
|
flyway_cols=""
|
|
if [ -f "$FLYWAY" ]; then
|
|
flyway_cols="$(awk -v t="$TABLE" '
|
|
$0 ~ "^CREATE TABLE (IF NOT EXISTS )?" t "([[:space:]]|\\()" { in_t=1; next }
|
|
in_t && /^\)/ { in_t=0 }
|
|
in_t {
|
|
line=$0
|
|
sub(/^[[:space:]]+/, "", line)
|
|
if (line ~ /^(INDEX|KEY|PRIMARY|CONSTRAINT|UNIQUE|FOREIGN)/) next
|
|
if (line ~ /^[A-Za-z0-9_]+[[:space:]]/) { split(line, a, /[[:space:]]/); print a[1] }
|
|
}
|
|
' "$FLYWAY" | sort -u | tr '\n' ' ')"
|
|
fi
|
|
|
|
check "$([ -f "$ENTITY" ] && echo 1 || echo 0)" "C1. 实体文件存在"
|
|
check "$([ -f "$FLYWAY" ] && echo 1 || echo 0)" "C2. 建表 Flyway 存在"
|
|
check "$([ -n "$entity_cols" ] && echo 1 || echo 0)" "C3. 实体 @Column 解析出非空列集合"
|
|
check "$([ -n "$flyway_cols" ] && echo 1 || echo 0)" "C4. Flyway 解析出非空列集合"
|
|
|
|
# 两个独立来源必须一致(否则说明解析失败或生产定义本身不一致)
|
|
check "$([ "$(printf '%s' "$entity_cols" | tr ' ' '\n' | grep -c .)" -gt 0 ] \
|
|
&& [ "$entity_cols" = "$flyway_cols" ] && echo 1 || echo 0)" \
|
|
"C5. 实体 @Column 与建表 Flyway 的列集合一致"
|
|
|
|
echo " 实体列 : ${entity_cols:-<空>}"
|
|
echo " Flyway列: ${flyway_cols:-<空>}"
|
|
echo ""
|
|
|
|
# ── 字段存在性(表限定)───────────────────────────────────────
|
|
check "$(case " $flyway_cols " in *" expired_at "*) echo 1 ;; *) echo 0 ;; esac)" \
|
|
"C6. sms_verification_codes 存在 expired_at 列"
|
|
check "$(case " $flyway_cols " in *" expires_at "*) echo 0 ;; *) echo 1 ;; esac)" \
|
|
"C7. sms_verification_codes **不存在** expires_at 列"
|
|
check "$(case " $entity_cols " in *" expired_at "*) echo 1 ;; *) echo 0 ;; esac)" \
|
|
"C8. 实体 expired_at 映射存在"
|
|
|
|
# ── 反向护栏:expires_at 确实是别的表的真实列 ──────────────────
|
|
# 若此条失败,说明有人把它当成了"全库不存在的列"——那会让本门禁退化成误报。
|
|
other_tables_with_expires_at=0
|
|
for f in "$MIGRATION_DIR"/V30__personal_data_export_tasks.sql \
|
|
"$MIGRATION_DIR"/V35__ability_assessment.sql \
|
|
"$MIGRATION_DIR"/V40__enterprise_talent_invitations.sql; do
|
|
[ -f "$f" ] || continue
|
|
grep -qE '^[[:space:]]*expires_at[[:space:]]' "$f" && other_tables_with_expires_at=$((other_tables_with_expires_at + 1))
|
|
done
|
|
check "$([ "$other_tables_with_expires_at" -gt 0 ] && echo 1 || echo 0)" \
|
|
"C9. expires_at 确实是其他表的真实列(故门禁必须按表限定,不得全局禁用)"
|
|
|
|
# ── 脚本侧:表名与列名 ────────────────────────────────────────
|
|
sql_block="$(awk '/SELECT code FROM sms_verification_codes/,/LIMIT 1;/' "$SMOKE" 2>/dev/null)"
|
|
# 只保留双引号内的 SQL 片段,剥掉 shell 语法(printf / 引号 / 续行反斜杠),
|
|
# 否则 printf、%s 之类会被误当成列名。
|
|
sql_text="$(printf '%s' "$sql_block" | grep -oE '"[^"]*"' | tr -d '"' | tr '\n' ' ')"
|
|
check "$([ -n "$sql_text" ] && echo 1 || echo 0)" "C10. 能从 smoke-test.sh 提取取码 SQL 文本"
|
|
|
|
check "$(printf '%s' "$sql_text" | grep -q "FROM $TABLE" && echo 1 || echo 0)" \
|
|
"C11. 取码 SQL 查询表为 $TABLE"
|
|
check "$(printf '%s' "$sql_text" | grep -q 'expired_at' && echo 1 || echo 0)" \
|
|
"C12. 取码 SQL 使用 expired_at"
|
|
check "$(printf '%s' "$sql_text" | grep -q 'expires_at' && echo 0 || echo 1)" \
|
|
"C13. 取码 SQL **不含** expires_at"
|
|
|
|
# 脚本 SQL 使用的每个列名都必须落在真实列集合内
|
|
used_cols="$(printf '%s' "$sql_text" \
|
|
| sed "s/'[^']*'//g" \
|
|
| grep -oE '\b[A-Za-z_][A-Za-z0-9_]*\b' \
|
|
| grep -viE '^(SELECT|code|FROM|WHERE|AND|OR|ORDER|BY|DESC|ASC|LIMIT|NOW)$|^'$TABLE'$' \
|
|
| sort -u | tr '\n' ' ')"
|
|
unknown=""
|
|
for c in $used_cols; do
|
|
case " $flyway_cols " in
|
|
*" $c "*) ;;
|
|
*) unknown="$unknown $c" ;;
|
|
esac
|
|
done
|
|
check "$([ -z "$unknown" ] && echo 1 || echo 0)" \
|
|
"C14. 取码 SQL 使用的列全部存在于 $TABLE${unknown:+(不存在的列:$unknown)}"
|
|
|
|
# 取码列 code 必须在真实列集合内(正向确认解析没跑偏)
|
|
check "$(case " $flyway_cols " in *" code "*) echo 1 ;; *) echo 0 ;; esac)" \
|
|
"C15. 取码 SQL 的列 code 是 $TABLE 的真实列"
|
|
|
|
echo ""
|
|
echo "── 真实验收阶段必须执行(本脚本不伪造该来源)───────────────"
|
|
echo "docker exec -i <mysql容器> sh -c \\"
|
|
echo " 'MYSQL_PWD=\"\$MYSQL_PASSWORD\" mysql -u\"\$MYSQL_USER\" -N -B \"\${MYSQL_DATABASE:-mydreamjob}\" <<<\"SHOW COLUMNS FROM $TABLE;\"'"
|
|
echo "对账要求:SHOW COLUMNS 的 Field 集合必须等于上方 Flyway列;"
|
|
echo " 必须包含 expired_at;必须不含 expires_at。"
|
|
echo ""
|
|
|
|
echo "============================================"
|
|
echo "PASS=$PASS FAIL=$FAIL"
|
|
echo "============================================"
|
|
[ "$FAIL" -eq 0 ]
|
|
|