From 497a4ebd7180b97cd66b5f78127fb23da6bb8b5e Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Sun, 25 Jan 2026 09:04:08 +0800 Subject: [PATCH] Add superadmin integration tests and env-based config --- Makefile | 12 ++- config/account-server.yaml | 3 +- config/account.yaml | 4 +- config/sync.example.yaml | 4 +- config/sync.yaml | 4 +- deploy/gcp/cloud-run/service.yaml | 4 +- integration-test/superadmin-login/README.md | 66 ++++++++++++ integration-test/superadmin-login/api-test.sh | 80 ++++++++++++++ .../superadmin-login/run-test-scripts.sh | 74 +++++++++++++ integration-test/superadmin-login/ui-test.sh | 101 ++++++++++++++++++ scripts/create-super-admin.sh | 34 +++++- scripts/setup_postgres_local.sh | 2 +- sql/readme.md | 34 +++--- 13 files changed, 390 insertions(+), 32 deletions(-) create mode 100644 integration-test/superadmin-login/README.md create mode 100755 integration-test/superadmin-login/api-test.sh create mode 100755 integration-test/superadmin-login/run-test-scripts.sh create mode 100755 integration-test/superadmin-login/ui-test.sh diff --git a/Makefile b/Makefile index 2c03600..091cd46 100644 --- a/Makefile +++ b/Makefile @@ -7,8 +7,12 @@ MAIN_FILE := ./cmd/accountsvc/main.go PORT ?= 8080 OS := $(shell uname -s) +# Load local environment overrides if present. +-include .env +export + DB_NAME := account -DB_USER ?= $(or $(POSTGRES_USER),shenlan) +DB_USER ?= $(or $(POSTGRES_USER),postgres) DB_PASS ?= $(or $(POSTGRES_PASSWORD),password) DB_HOST := 127.0.0.1 DB_PORT := 15432 @@ -54,7 +58,7 @@ export APP_NAME MAIN_FILE PORT OS \ # 🧩 基础命令 # ========================================= -.PHONY: all init build clean start stop restart dev test help \ +.PHONY: all init build clean start stop restart dev test help integration-test \ init-go init-db init-db-core init-db-replication init-db-pglogical \ stunnel-start \ reinit-pglogical account-sync-push account-sync-pull account-sync-mirror create-db-user db-reset \ @@ -77,6 +81,7 @@ help: @echo "make reinit-pglogical 重新初始化 pglogical schema" @echo "make dev 热重载开发模式" @echo "make clean 清理构建产物" + @echo "make integration-test 运行集成测试用例(初始化 + 创建管理员)" @echo "make cloudrun-build 构建并推送 Cloud Run 镜像" @echo "make cloudrun-deploy 部署 Cloud Run Service" @echo "make cloudrun-stunnel 更新 Cloud Run stunnel 配置 secret" @@ -168,6 +173,9 @@ account-sync-mirror: create-super-admin: @bash scripts/create-super-admin.sh +integration-test: + @bash integration-test/superadmin-login/run-test-scripts.sh + # ========================================= # ⚙️ 编译与运行 # ========================================= diff --git a/config/account-server.yaml b/config/account-server.yaml index aa962a8..eba60e4 100644 --- a/config/account-server.yaml +++ b/config/account-server.yaml @@ -31,7 +31,8 @@ server: store: driver: "postgres" - dsn: "postgres://shenlan:password@127.0.0.1:5432/account?sslmode=disable" + # 提示:本地默认从 .env 读取 POSTGRES_USER / POSTGRES_PASSWORD + dsn: "postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@127.0.0.1:5432/account?sslmode=disable" maxOpenConns: 30 maxIdleConns: 10 diff --git a/config/account.yaml b/config/account.yaml index 03b42d8..acb3c29 100644 --- a/config/account.yaml +++ b/config/account.yaml @@ -24,6 +24,7 @@ server: - "https://www.svc.plus" - "https://global-homepage.svc.plus" - "https://accounts.svc.plus" + - "https://console.svc.plus" - "https://localhost:8443" - "http://localhost:8080" - "http://127.0.0.1:8080" @@ -41,7 +42,8 @@ server: store: driver: "postgres" - dsn: "postgres://shenlan:password@127.0.0.1:5432/account?sslmode=disable" + # 提示:本地默认从 .env 读取 POSTGRES_USER / POSTGRES_PASSWORD + dsn: "postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@127.0.0.1:5432/account?sslmode=disable" maxOpenConns: 30 maxIdleConns: 10 diff --git a/config/sync.example.yaml b/config/sync.example.yaml index 82b437e..a3f82f7 100644 --- a/config/sync.example.yaml +++ b/config/sync.example.yaml @@ -10,7 +10,8 @@ local: # 本地 PostgreSQL 连接地址,用于导入/导出账号数据 - dsn: "postgres://shenlan:password@127.0.0.1:5432/account?sslmode=disable" + # 提示:本地默认从 .env 读取 POSTGRES_USER / POSTGRES_PASSWORD + dsn: "postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@127.0.0.1:5432/account?sslmode=disable" # 可选:按 email 关键字过滤导出的账号 email_keyword: "" # 导出的快照文件路径(默认 account-export.yaml) @@ -47,4 +48,3 @@ remote: # SSH 连接超时时间 timeout: 30s - diff --git a/config/sync.yaml b/config/sync.yaml index c7c261d..0b31df6 100644 --- a/config/sync.yaml +++ b/config/sync.yaml @@ -10,7 +10,8 @@ local: # 本地 PostgreSQL 连接地址,用于导入/导出账号数据 - dsn: "postgres://shenlan:password@127.0.0.1:5432/account?sslmode=disable" + # 提示:本地默认从 .env 读取 POSTGRES_USER / POSTGRES_PASSWORD + dsn: "postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@127.0.0.1:5432/account?sslmode=disable" # 可选:按 email 关键字过滤导出的账号 email_keyword: "" # 导出的快照文件路径(默认 account-export.yaml) @@ -47,4 +48,3 @@ remote: # SSH 连接超时时间 timeout: 30s - diff --git a/deploy/gcp/cloud-run/service.yaml b/deploy/gcp/cloud-run/service.yaml index 1e7a195..28c4de8 100644 --- a/deploy/gcp/cloud-run/service.yaml +++ b/deploy/gcp/cloud-run/service.yaml @@ -20,7 +20,7 @@ spec: containers: # --- 主应用容器 --- - name: accounts-api - image: asia-northeast1-docker.pkg.dev/xzerolab-480008/cloud-run-source-deploy/accounts.svc.plus/accounts-svc-plus:d5b6edcaa76150f2489bbdb2a46a41bc98baa87f + image: asia-northeast1-docker.pkg.dev/xzerolab-480008/cloud-run-source-deploy/accounts.svc.plus/accounts-svc-plus:latest ports: - name: http1 containerPort: 8080 @@ -48,7 +48,7 @@ spec: - name: POSTGRES_USER value: postgres - name: DB_NAME - value: postgres + value: account resources: limits: cpu: 1000m diff --git a/integration-test/superadmin-login/README.md b/integration-test/superadmin-login/README.md new file mode 100644 index 0000000..981e01c --- /dev/null +++ b/integration-test/superadmin-login/README.md @@ -0,0 +1,66 @@ +# superadmin-login + +## 说明 + +该用例用于本地集成测试,按顺序执行: + +1. `make init-db` +2. `make create-db-user` +3. `make create-super-admin` + +脚本会自动读取项目根目录 `.env` 中的环境变量(如 `POSTGRES_USER` / `POSTGRES_PASSWORD`),用于联动数据库配置。 +若未设置 `SUPERADMIN_PASSWORD`,脚本会生成随机密码并写回 `.env`,便于后续登录测试复用。 + +## 运行方式 + +```bash +make integration-test +``` + +或直接运行脚本: + +```bash +bash integration-test/superadmin-login/run-test-scripts.sh +``` + +## API 自动化测试 + +```bash +bash integration-test/superadmin-login/api-test.sh +``` + +可选环境变量: + +- `API_BASE_URL`:API 入口地址(默认 `https://accounts.svc.plus`) +- `LOGIN_EMAIL`:登录邮箱(默认 `admin@svc.plus`) +- `SUPERADMIN_PASSWORD`:登录密码(从 `.env` 读取) + +## UI 自动化测试(Playwright) + +```bash +bash integration-test/superadmin-login/ui-test.sh +``` + +可选环境变量: + +- `UI_BASE_URL`:UI 入口地址(默认 `https://console.svc.plus`) +- `LOGIN_EMAIL`:登录邮箱(默认 `admin@svc.plus`) +- `SUPERADMIN_PASSWORD`:登录密码(从 `.env` 读取) + +## 预期输出(示例) + +``` +✅ 已读取 .env +>>> init-db +...(略) +>>> create-super-admin +...(略) +✅ 集成测试步骤完成(DB 初始化 + 用户创建 + 超级管理员创建) + +接下来手动登录验证: +- 网址:https://console.svc.plus/login +- 邮箱:admin@svc.plus +- 密码: +``` + +> 注意:登录验证需要手动在浏览器完成。 diff --git a/integration-test/superadmin-login/api-test.sh b/integration-test/superadmin-login/api-test.sh new file mode 100755 index 0000000..18a76cb --- /dev/null +++ b/integration-test/superadmin-login/api-test.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT_DIR" + +if [ -f .env ]; then + set -a + # shellcheck disable=SC1091 + source .env + set +a + echo "✅ 已读取 .env" +fi + +if ! command -v curl >/dev/null; then + echo "❌ 未找到 curl,请先安装或确保在 PATH 中" >&2 + exit 1 +fi + +API_BASE_URL="${API_BASE_URL:-${BASE_URL:-https://accounts.svc.plus}}" +LOGIN_EMAIL="${LOGIN_EMAIL:-admin@svc.plus}" +LOGIN_PASSWORD="${SUPERADMIN_PASSWORD:-}" + +if [ -z "$LOGIN_PASSWORD" ]; then + echo "❌ 缺少 SUPERADMIN_PASSWORD(可写入 .env)" >&2 + exit 1 +fi + +login_payload=$(cat <&2 + echo "$login_body" >&2 + exit 1 +fi + +if command -v python3 >/dev/null; then + token=$(printf "%s" "$login_body" | python3 - <<'PY' +import json, sys +try: + payload = json.load(sys.stdin) + print(payload.get('token','')) +except Exception: + print('') +PY +) +else + echo "❌ 未找到 python3,无法解析登录 token" >&2 + exit 1 +fi + +if [ -z "$token" ]; then + echo "❌ 登录响应中未包含 token" >&2 + echo "$login_body" >&2 + exit 1 +fi + +session_response=$(curl -sS -w "\n%{http_code}" -X GET "${API_BASE_URL}/api/auth/session" \ + -H "Authorization: Bearer ${token}") + +session_body=$(printf "%s" "$session_response" | sed '$d') +session_status=$(printf "%s" "$session_response" | tail -n1) + +if [ "$session_status" != "200" ]; then + echo "❌ session 校验失败: HTTP ${session_status}" >&2 + echo "$session_body" >&2 + exit 1 +fi + +echo "✅ API 登录测试通过" diff --git a/integration-test/superadmin-login/run-test-scripts.sh b/integration-test/superadmin-login/run-test-scripts.sh new file mode 100755 index 0000000..9856e3a --- /dev/null +++ b/integration-test/superadmin-login/run-test-scripts.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT_DIR" + +if [ -f .env ]; then + set -a + # shellcheck disable=SC1091 + source .env + set +a + echo "✅ 已读取 .env" +fi + +ensure_superadmin_password() { + if [ -n "${SUPERADMIN_PASSWORD:-}" ] && [ "${SUPERADMIN_PASSWORD}" != "ChangeMe" ]; then + return 0 + fi + + if command -v openssl >/dev/null; then + SUPERADMIN_PASSWORD="$(openssl rand -base64 12 | tr -d '\n' | tr '/+' 'Aa' | cut -c1-10)" + else + SUPERADMIN_PASSWORD="$(LC_ALL=C tr -dc 'A-Za-z0-9' /dev/null; then + echo "❌ 未找到 node,请先安装" >&2 + exit 1 +fi + +if ! command -v npx >/dev/null; then + echo "❌ 未找到 npx,请先安装" >&2 + exit 1 +fi + +UI_BASE_URL="${UI_BASE_URL:-${BASE_URL:-https://console.svc.plus}}" +LOGIN_EMAIL="${LOGIN_EMAIL:-admin@svc.plus}" +LOGIN_PASSWORD="${SUPERADMIN_PASSWORD:-}" + +if [ -z "$LOGIN_PASSWORD" ]; then + echo "❌ 缺少 SUPERADMIN_PASSWORD(可写入 .env)" >&2 + exit 1 +fi + +TMP_DIR="${TMPDIR:-/tmp}" +PLAYWRIGHT_TEST_DIR="${TMP_DIR}/xcontrol-playwright-login" +mkdir -p "$PLAYWRIGHT_TEST_DIR" + +cat <<'TEST' > "$PLAYWRIGHT_TEST_DIR/login.spec.mjs" +import { test, expect } from '@playwright/test'; + +test.use({ screenshot: 'only-on-failure' }); + +test('superadmin login', async ({ page }) => { + const baseUrl = process.env.UI_BASE_URL || process.env.BASE_URL || 'https://console.svc.plus'; + const email = process.env.LOGIN_EMAIL || 'admin@svc.plus'; + const password = process.env.SUPERADMIN_PASSWORD; + + if (!password) { + throw new Error('missing SUPERADMIN_PASSWORD'); + } + + await page.goto(`${baseUrl}/login`, { waitUntil: 'domcontentloaded' }); + + const emailByRole = page.getByRole('textbox', { name: /email|邮箱|账号|用户名|identifier/i }); + const emailBySelector = page.locator( + 'input[type="email"], input[name="email"], input[name="identifier"], input[placeholder*="邮箱"], input[placeholder*="Email"], input[type="text"]' + ); + const emailInput = (await emailByRole.count()) > 0 ? emailByRole.first() : emailBySelector.first(); + await expect(emailInput).toBeVisible({ timeout: 15000 }); + await emailInput.fill(email); + + const passwordInput = page.locator( + 'input[type="password"], input[name="password"], input[placeholder*="密码"], input[placeholder*="Password"]' + ); + await expect(passwordInput).toBeVisible({ timeout: 15000 }); + await passwordInput.fill(password); + + const submitBtn = page.locator( + 'button[type="submit"], button:has-text("登录"), button:has-text("Log in"), button:has-text("Sign in")' + ); + await expect(submitBtn).toBeVisible({ timeout: 15000 }); + await submitBtn.click(); + + await page.waitForLoadState('networkidle'); + await expect(page).not.toHaveURL(/login/); +}); +TEST + +cd "$PLAYWRIGHT_TEST_DIR" + +if [ ! -f package.json ]; then + cat <<'PKG' > package.json +{ + "name": "xcontrol-playwright-login", + "private": true, + "version": "0.0.0", + "type": "module", + "devDependencies": { + "@playwright/test": "^1.49.0" + } +} +PKG +fi + +npm install --silent +npx playwright install chromium + +UI_BASE_URL="$UI_BASE_URL" BASE_URL="$UI_BASE_URL" LOGIN_EMAIL="$LOGIN_EMAIL" SUPERADMIN_PASSWORD="$LOGIN_PASSWORD" \ + npx playwright test login.spec.mjs + +echo "✅ UI 登录测试通过" diff --git a/scripts/create-super-admin.sh b/scripts/create-super-admin.sh index 1aa9906..9f0ac69 100755 --- a/scripts/create-super-admin.sh +++ b/scripts/create-super-admin.sh @@ -3,13 +3,34 @@ set -euo pipefail source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh" -if [ -z "${SUPERADMIN_USERNAME}" ] || [ -z "${SUPERADMIN_PASSWORD}" ]; then - echo "❌ 请指定用户名与密码" - exit 1 +SUPERADMIN_EMAIL="admin@svc.plus" +SUPERADMIN_USERNAME="${SUPERADMIN_USERNAME:-Admin}" + +if [ -z "${SUPERADMIN_PASSWORD:-}" ] || [ "${SUPERADMIN_PASSWORD}" = "ChangeMe" ]; then + if command -v openssl >/dev/null; then + SUPERADMIN_PASSWORD="$(openssl rand -base64 12 | tr -d '\n' | tr '/+' 'Aa' | cut -c1-10)" + else + SUPERADMIN_PASSWORD="$(LC_ALL=C tr -dc 'A-Za-z0-9'