test(19.5): gate the email_verified_at migration and TestOwnerVerifiedEmail

- USER_ADDED_MIGRATIONS allows the one additive migration (D-27 relaxes D-22)
- refused_migration_changes and migration_writes_rows, with --self-test cases
- TestOwnerVerifiedEmail required in the docker stage
This commit is contained in:
Jakub Zych
2026-10-21 13:56:05 +02:00
parent 33460607db
commit 7170c5bac6

View File

@@ -13,8 +13,11 @@
# application workspace), its Docker-free tests by required
# name, and the additive check: since PHASE195_USER_BASE no
# line declaring an exported func, method or type was removed
# from a non-test .go file, and no file under updates/ changed
# (D-22).
# from a non-test .go file, and the migration check: the only
# non-test change under updates/ is the added
# email_verified_at migration (USER_ADDED_MIGRATIONS, D-22
# relaxed by D-27), which must add its column with ADD
# COLUMN IF NOT EXISTS and write or drop nothing.
# sociallogin go vet and go test -short in the social login plugin, its
# Docker-free tests by required name, no gorilla/sessions and
# no gopkg.in/yaml.v3 in `go list -deps .`, and no Go file
@@ -69,6 +72,11 @@ DEMO_APP="${DEMO_APP:-$ROOT/../sm-demo-app}"
BM_APP="${BM_APP:-$ROOT/../sm-bm-app}"
# The user plugin commit the phase started from (PATTERNS.md).
USER_BASE="${PHASE195_USER_BASE:-4c5d3b8}"
# The migrations the user plugin may add since USER_BASE. D-22 allowed none;
# D-27 relaxes it for this one additive migration (users.email_verified_at,
# a nullable column, no row written). Any other added, modified, renamed or
# deleted non-test file under updates/ is refused.
USER_ADDED_MIGRATIONS=(updates/202610210001_add_users_email_verified_at.go)
# ---------------------------------------------------------------------------
# Required test names. Each list runs with -run '^(a|b|...)$' and every name
@@ -142,6 +150,9 @@ DOCKER_SOCIAL_TESTS=(
# Plan 04: the PHP ports, one subtest per PHP method.
TestPHPSocialAuthGate TestPHPOAuthLinkCookie TestPHPPendingRegistrationTakeover
TestPHPPasswordBootstrap TestPHPProfileOAuthLink
# Plan 05 (D-26): a verified email links only onto an account whose
# owner proved the address (users.email_verified_at).
TestOwnerVerifiedEmail
)
# docs stage: names of consuming applications (split so this file does not
@@ -346,6 +357,34 @@ forbidden_deps() {
grep -E '^(github\.com/gorilla/sessions|gopkg\.in/yaml\.v3)(/|$)' || true
}
# refused_migration_changes reads `git diff --name-status` lines and prints
# every line whose status is not A or whose path is not one of
# USER_ADDED_MIGRATIONS (D-22, D-27). A rename (R...) or copy carries two
# paths and is always refused.
refused_migration_changes() {
local status path rest allowed ok
while IFS=$'\t' read -r status path rest || [[ -n "$status" ]]; do
[[ -n "$status" ]] || continue
ok=0
if [[ "$status" == "A" && -z "$rest" ]]; then
for allowed in "${USER_ADDED_MIGRATIONS[@]}"; do
if [[ "$path" == "$allowed" ]]; then
ok=1
fi
done
fi
if [[ "$ok" -eq 0 ]]; then
printf '%s\t%s%s\n' "$status" "$path" "${rest:+$'\t'$rest}"
fi
done
}
# migration_writes_rows reads the Go file $1 without its // comment lines
# and prints every line carrying an SQL word that writes or drops data.
migration_writes_rows() {
grep -vE '^[[:space:]]*//' "$1" | grep -E '\b(UPDATE|INSERT|DELETE|DROP|TRUNCATE)\b' || true
}
run_self_test() {
bash -n "${BASH_SOURCE[0]}"
@@ -424,6 +463,39 @@ run_self_test() {
return 1
fi
# The migration check (D-22, D-27) accepts only the added
# email_verified_at migration and refuses every other change under
# updates/, and a migration that writes or drops anything.
if [[ -n "$(refused_migration_changes <<<$'A\tupdates/202610210001_add_users_email_verified_at.go')" ]]; then
echo "refuse: self-test refused_migration_changes refused the allowed migration" >&2
return 1
fi
for line in $'M\tupdates/00_base.go' $'A\tupdates/202610220001_add_users_other.go' \
$'D\tupdates/202610210001_add_users_email_verified_at.go' \
$'R100\tupdates/202610160002_add_handle_to_users.go\tupdates/202610210001_add_users_email_verified_at.go'; do
if [[ -z "$(refused_migration_changes <<<"$line")" ]]; then
echo "refuse: self-test refused_migration_changes missed: $line" >&2
return 1
fi
done
local mig
mig="$(mktemp -d)"
printf '%s\n' 'package updates' '' 'func m(tx *gorm.DB) error {' \
' return tx.Exec(`UPDATE users SET email_verified_at = now()`).Error' '}' >"$mig/writes.go"
printf '%s\n' 'package updates' '' '// No UPDATE here: the migration writes no rows.' 'func m(tx *gorm.DB) error {' \
' return tx.Exec(`ALTER TABLE users ADD COLUMN IF NOT EXISTS email_verified_at TIMESTAMPTZ NULL`).Error' '}' >"$mig/clean.go"
if [[ -z "$(migration_writes_rows "$mig/writes.go")" ]]; then
rm -rf "$mig"
echo "refuse: self-test migration_writes_rows missed a row update" >&2
return 1
fi
if [[ -n "$(migration_writes_rows "$mig/clean.go")" ]]; then
rm -rf "$mig"
echo "refuse: self-test migration_writes_rows refused a word in a comment" >&2
return 1
fi
rm -rf "$mig"
local mode
for mode in self-test go userplugin sociallogin demo bm fonoteka docker docs all; do
grep -q -- "^ --$mode" "${BASH_SOURCE[0]}" || {
@@ -464,19 +536,37 @@ run_userplugin() {
return 1
fi
added="$(grep -cE '^\+(func|type) ' <<<"$diff" || true)"
# D-22, D-27: under updates/ only the email_verified_at migration may be
# added (test files aside), and it adds its column and writes nothing.
local refused file writes checked=0
rc=0
migrations="$(git -C "$USER_PLUGIN" diff --name-only "$USER_BASE" -- updates/ 2>&1)" || rc=$?
migrations="$(git -C "$USER_PLUGIN" diff --name-status "$USER_BASE" -- updates/ ':(exclude)*_test.go' 2>&1)" || rc=$?
if [[ "$rc" -ne 0 ]]; then
printf '%s\n' "$migrations" | tail -n 5 >&2
echo "refuse: git diff --name-only $USER_BASE -- updates/ in $USER_PLUGIN failed (exit $rc)" >&2
echo "refuse: git diff --name-status $USER_BASE -- updates/ in $USER_PLUGIN failed (exit $rc)" >&2
return 1
fi
if [[ -n "$migrations" ]]; then
printf '%s\n' "$migrations" >&2
echo "refuse: the user plugin changed files under updates/ since $USER_BASE (D-22: no migration change)" >&2
refused="$(refused_migration_changes <<<"$migrations")"
if [[ -n "$refused" ]]; then
printf '%s\n' "$refused" >&2
echo "refuse: the user plugin changed files under updates/ since $USER_BASE (D-22, D-27: only the email_verified_at migration may be added)" >&2
return 1
fi
echo "additive: no exported func, method or type removed and no migration changed in the user plugin since $USER_BASE ($added declarations added)"
for file in "${USER_ADDED_MIGRATIONS[@]}"; do
[[ -f "$USER_PLUGIN/$file" ]] || continue
checked=$((checked + 1))
if ! grep -q 'ADD COLUMN IF NOT EXISTS email_verified_at' "$USER_PLUGIN/$file"; then
echo "refuse: $file does not add email_verified_at with ADD COLUMN IF NOT EXISTS (D-27)" >&2
return 1
fi
writes="$(migration_writes_rows "$USER_PLUGIN/$file")"
if [[ -n "$writes" ]]; then
printf '%s\n' "$writes" >&2
echo "refuse: $file writes or drops data (D-27: additive, no backfill)" >&2
return 1
fi
done
echo "additive: no exported func, method or type removed and no migration changed beyond the allowed additions in the user plugin since $USER_BASE ($added declarations added; $checked allowed migration(s) added, column only)"
echo "phase19.5 userplugin passed"
}