Files
opsxdb/tests/verify-crud.sh
T
logan a9510235bb
CI / Go server (push) Canceled after 0s
CI / Web + core packages (push) Canceled after 0s
CI / Desktop (Tauri) cargo check (push) Canceled after 0s
feat: 18 引擎驱动全家桶 + 驱动管理/离线包 + 设置中心 + 工作台交互打磨
驱动接入(全部本机真实例验收):
- Elasticsearch 原生 SQL API、InfluxDB 原生 HTTP+Flux(官方 JDBC 付费许可,实测弃用)
- ZooKeeper/MongoDB JVM agent(连接时自动下载官方驱动 jar,sha256 钉版本到 ~/.opsxdb/drivers/)
- 达梦 JDBC agent 桥就绪(待厂商 jar+实例);TiDB 真实例验收
- 协议族代码审查放行 PolarDB/TDSQL/OceanBase/Kingbase/GaussDB/Oracle
- 驱动能力模型:按驱动开关对象树分组/右键菜单/行编辑,NoSQL 树拍平(对象直挂库节点)

驱动管理:
- 驱动管理弹框(状态/预装/离线包导入/JDK 检测)+ /api/drivers/* 三接口
- opsxdb-driver 驱动商店仓库(清单/fetch.sh/pack.sh 离线包/import.sh)
- 连接时懒加载:DMG 零 Java 产物,首连自动下载(实测 ZK 九件套 1.35s),离线包自动回退

UI/交互:
- 设置中心(⚙):常规/执行/结果/驱动/快捷键/关于,左导航+右面板(对标 opsxshell)
- 实例级展开箭头(行首 SVG chevron,替代字体回退失真的 Unicode 三角)
- 树内嵌套渲染修复:对象树挂在激活连接下;工具栏窄窗口不断行(按钮 nowrap+整颗换行)
- 侧栏连接行精简(图标+连接名)、SQLite SSH 守卫、Oracle 死参数清理

质量:
- Go 协议层/离线回退单测、E2E 门控测试(ES/ZK/Influx/Mongo/TiDB/JDBC 桥)
- 驱动注册表单一事实来源 connection-types.json,README/BACKLOG/AGENTS 同步
2026-09-22 01:07:46 +08:00

314 lines
10 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# OpsxDB 驱动验收:一次只起一个测试库(见 README.md)。
# ./verify-crud.sh # 按 sqlite → a → b → c → f → d → e 顺序,每步只留一个容器
# ./verify-crud.sh a # 只验 MySQL
# ./verify-crud.sh f # 只验 Redis
set -uo pipefail
API="${OPSXDB_API:-http://127.0.0.1:8788}"
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$(dirname "$0")"
REPORT="$ROOT/tests/accept-result.txt"
: >"$REPORT"
n_ok=0; n_fail=0
ok() { echo " OK $*"; echo " OK $*" >>"$REPORT"; n_ok=$((n_ok+1)); }
bad() { echo " FAIL $*"; echo " FAIL $*" >>"$REPORT"; n_fail=$((n_fail+1)); }
log() { echo "$*"; echo "$*" >>"$REPORT"; }
post() {
curl -sS -m 45 -H 'Content-Type: application/json' -d "$1" "$API$2"
}
json_ok() {
python3 -c 'import json,sys
d=json.load(sys.stdin)
if d.get("needConfirm"): sys.exit(2)
err=d.get("error")
if err: sys.exit(1)
if "ok" in d and d.get("ok") is False: sys.exit(1)
sys.exit(0)'
}
wait_health() {
local name="$1" tries="${2:-60}"
for i in $(seq 1 "$tries"); do
local st
st=$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$name" 2>/dev/null || echo missing)
if [[ "$st" == "healthy" ]]; then return 0; fi
if [[ "$st" == "running" && "$tries" -lt 5 ]]; then return 0; fi
sleep 2
done
echo " (health=$st after ${tries} tries)"
[[ "$st" == "running" || "$st" == "healthy" ]]
}
assert_single_container() {
local expect="$1"
local running
running=$(docker ps --filter name=opsxdb- --format '{{.Names}}' | grep -v '^$' || true)
local n
n=$(echo "$running" | grep -c . || true)
if [[ -z "$expect" ]]; then
[[ "$n" -eq 0 ]] && ok "无测试库容器" || bad "仍有容器: $running"
return
fi
if [[ "$n" -eq 1 ]] && echo "$running" | grep -qx "$expect"; then
ok "仅 $expect 在跑"
else
bad "期望仅 ${expect},实际: ${running:-none}"
fi
}
up_one() {
local alias="$1" container="$2" tries="${3:-40}"
./dbctl.sh up "$alias"
wait_health "$container" "$tries" || true
assert_single_container "$container"
}
exec_sql() {
local base="$1" sql="$2"
python3 -c 'import json,sys
d=json.loads(sys.argv[1]); d["sql"]=sys.argv[2]; print(json.dumps(d))' "$base" "$sql"
}
crud_sql() {
local driver="$1" host="$2" port="$3" user="$4" password="$5" db="$6" dialect="$7"
local base
base=$(printf '{"driver":"%s","host":"%s","port":%s,"user":"%s","password":"%s","database":"%s"}' \
"$driver" "$host" "$port" "$user" "$password" "$db")
log "== $driver =="
local tc
tc=$(post "$base" /api/db/test-connection)
echo "$tc" | json_ok && ok "test-connection" || { bad "test-connection $tc"; return; }
local dbs
dbs=$(post "$base" /api/db/databases)
echo "$dbs" | json_ok && ok "databases" || bad "databases $dbs"
local tables
tables=$(post "$base" /api/db/tables)
echo "$tables" | json_ok && ok "tables" || bad "tables $tables"
local sel ins upd del
case "$dialect" in
mysql|postgres|sqlite)
sel='SELECT id,name FROM users ORDER BY id LIMIT 5'
ins="INSERT INTO users (name, email, status) VALUES ('grace','grace@demo.local',1)"
upd="UPDATE users SET status=0 WHERE name='grace'"
del="DELETE FROM users WHERE name='grace'"
;;
mssql)
sel='SELECT TOP 5 id,name FROM dbo.users ORDER BY id'
ins="INSERT INTO dbo.users (name, email, status) VALUES (N'grace',N'grace@demo.local',1)"
upd="UPDATE dbo.users SET status=0 WHERE name=N'grace'"
del="DELETE FROM dbo.users WHERE name=N'grace'"
;;
oracle)
sel='SELECT id,name FROM users ORDER BY id FETCH FIRST 5 ROWS ONLY'
ins="INSERT INTO users (name, email, status) VALUES ('grace','grace@demo.local',1)"
upd="UPDATE users SET status=0 WHERE name='grace'"
del="DELETE FROM users WHERE name='grace'"
;;
redis)
sel='PING'
ins='SET opsxdb:demo:grace hello'
upd='GET opsxdb:demo:grace'
del='DEL opsxdb:demo:grace'
;;
*)
bad "unknown dialect $dialect"; return
;;
esac
local body r
body=$(exec_sql "$base" "$sel")
r=$(post "$body" /api/db/exec)
echo "$r" | json_ok && ok "SELECT/PING" || bad "SELECT/PING $r"
body=$(exec_sql "$base" "$ins")
r=$(post "$body" /api/db/exec)
echo "$r" | json_ok && ok "INSERT/SET" || bad "INSERT/SET $r"
body=$(exec_sql "$base" "$upd")
r=$(post "$body" /api/db/exec)
echo "$r" | json_ok && ok "UPDATE/GET" || bad "UPDATE/GET $r"
body=$(exec_sql "$base" "$del")
r=$(post "$body" /api/db/exec)
echo "$r" | json_ok && ok "DELETE/DEL" || bad "DELETE/DEL $r"
if [[ "$dialect" == "redis" ]]; then
local keys kv scan
keys=$(post "$base" /api/db/redis/keys)
echo "$keys" | json_ok && ok "redis/keys" || bad "redis/keys $keys"
body=$(python3 -c 'import json,sys; d=json.loads(sys.argv[1]); d["sql"]="SET CEX:FOLLOW:k1 v"; print(json.dumps(d))' "$base")
post "$body" /api/db/exec >/dev/null
kv=$(python3 -c 'import json,sys; d=json.loads(sys.argv[1]); d["key"]="CEX:FOLLOW:k1"; print(json.dumps(d))' "$base")
kv=$(post "$kv" /api/db/redis/key)
echo "$kv" | json_ok && ok "redis/key" || bad "redis/key $kv"
body=$(exec_sql "$base" "SCAN 0 MATCH CEX:* COUNT 50")
scan=$(post "$body" /api/db/exec)
echo "$scan" | json_ok && ok "SCAN MATCH" || bad "SCAN MATCH $scan"
fi
}
run_sqlite() {
log
log "---- sqlite(无 Docker)----"
local SQLITE_DB="$ROOT/tests/tmp/demo.sqlite"
mkdir -p "$ROOT/tests/tmp"
rm -f "$SQLITE_DB"
if ! command -v sqlite3 >/dev/null 2>&1; then
bad "本机无 sqlite3"
return
fi
sqlite3 "$SQLITE_DB" < init/sqlite/01_demo.sql
crud_sql sqlite "" 0 "" "" "$SQLITE_DB" sqlite
}
run_mysql() {
log
log "---- a MySQL :3306 ----"
up_one a opsxdb-mysql 40
docker exec -i opsxdb-mysql mysql -uroot -p'Demo@123' < init/mysql/01_demo.sql 2>/dev/null
crud_sql mysql 127.0.0.1 3306 root 'Demo@123' demo mysql
}
run_mariadb() {
log
log "---- b MariaDB :3307 ----"
up_one b opsxdb-mariadb 40
docker exec -i opsxdb-mariadb mariadb -uroot -p'Demo@123' < init/mariadb/01_demo.sql 2>/dev/null \
|| docker exec -i opsxdb-mariadb mysql -uroot -p'Demo@123' < init/mariadb/01_demo.sql 2>/dev/null
crud_sql mariadb 127.0.0.1 3307 root 'Demo@123' demo mysql
}
run_postgres() {
log
log "---- c PostgreSQL :5432 ----"
up_one c opsxdb-postgres 40
docker exec -i opsxdb-postgres psql -U opsx -d demo < init/postgres/01_demo.sql >/dev/null
crud_sql postgresql 127.0.0.1 5432 opsx 'Demo@123' demo postgres
}
run_redis() {
log
log "---- f Redis :6379 ----"
up_one f opsxdb-redis 30
docker exec opsxdb-redis redis-cli SET LANG:zh-CN hello >/dev/null
docker exec opsxdb-redis redis-cli HSET CEX:FOLLOW:FOLLOWERS:1 f1 '{"a":1}' >/dev/null
crud_sql redis 127.0.0.1 6379 "" "" 0 redis
}
run_mssql() {
log
log "---- d SQL Server :1433 ----"
up_one d opsxdb-mssql 120
local ready=0
for i in $(seq 1 60); do
if docker exec opsxdb-mssql /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'Demo@12345' -C -Q 'SELECT 1' >/dev/null 2>&1 \
|| docker exec opsxdb-mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'Demo@12345' -Q 'SELECT 1' >/dev/null 2>&1; then
ready=1
break
fi
sleep 3
done
if [[ "$ready" -ne 1 ]]; then
bad "sqlcmd 未就绪"
return
fi
ok "sqlcmd ready"
docker cp init/mssql/01_demo.sql opsxdb-mssql:/tmp/01_demo.sql
docker exec opsxdb-mssql /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'Demo@12345' -C -i /tmp/01_demo.sql >/dev/null 2>&1 \
|| docker exec opsxdb-mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'Demo@12345' -i /tmp/01_demo.sql >/dev/null 2>&1 \
|| true
local base_master='{"driver":"sqlserver","host":"127.0.0.1","port":1433,"user":"sa","password":"Demo@12345","database":"master"}'
local r
r=$(post "$base_master" /api/db/test-connection)
echo "$r" | json_ok && ok "test-connection master" || bad "test-connection master $r"
crud_sql sqlserver 127.0.0.1 1433 sa 'Demo@12345' demo mssql
}
run_oracle() {
log
log "---- e Oracle :1521(Apple Silicon 常 ORA-00442)----"
up_one e opsxdb-oracle 180
docker cp init/oracle/01_demo.sql opsxdb-oracle:/tmp/01_demo.sql 2>/dev/null || true
docker exec opsxdb-oracle bash -lc "echo exit | sqlplus -s opsx/Demo@123@//localhost/XEPDB1 @/tmp/01_demo.sql" >/dev/null 2>&1 \
|| docker exec opsxdb-oracle bash -lc "echo exit | sqlplus -s system/Demo@123@//localhost/XEPDB1 @/tmp/01_demo.sql" >/dev/null 2>&1 \
|| true
crud_sql oracle 127.0.0.1 1521 opsx 'Demo@123' XEPDB1 oracle
}
run_grey() {
local alias="$1" name="$2" container="$3" driver="$4" port="$5"
log
log "---- $alias $name(驱动未接入,只验测连失败/灰态)----"
up_one "$alias" "$container" 40
local base
base=$(printf '{"driver":"%s","host":"127.0.0.1","port":%s}' "$driver" "$port")
local r
r=$(post "$base" /api/db/test-connection)
if echo "$r" | grep -qiE '未接入|未就绪|not ready|unknown driver|unsupported'; then
ok "灰态测连被拒绝"
else
echo "$r" | json_ok && bad "未接入驱动却测连成功 $r" || ok "测连失败(驱动未接入): $r"
fi
}
usage() {
cat <<USAGE
用法: $0 [目标]
无参数 顺序验收 sqlite a b c f d e(每步只留一个容器)
sqlite 仅 SQLite 文件库
a|mysql MySQL :3306
b|mariadb MariaDB :3307
c|postgres PostgreSQL :5432
d|mssql SQL Server :1433
e|oracle Oracle :1521
f|redis Redis :6379
g|h|i ZooKeeper / ES / Influx(驱动未接入,只验灰态)
结果写入 tests/accept-result.txt
USAGE
}
log "API=$API $(date '+%Y-%m-%d %H:%M:%S')"
if ! curl -sS -m 5 "$API/api/health" >/dev/null; then
log "API down — 请先在仓库根目录 ./run.sh web"
exit 1
fi
ok "API health"
target="${1:-all}"
case "$target" in
-h|--help|help) usage; exit 0 ;;
sqlite) run_sqlite ;;
a|mysql) run_mysql ;;
b|mariadb|maria) run_mariadb ;;
c|postgres|pg|postgresql) run_postgres ;;
d|mssql|sqlserver) run_mssql ;;
e|oracle) run_oracle ;;
f|redis) run_redis ;;
g|zk|zookeeper) run_grey g ZooKeeper opsxdb-zookeeper zookeeper 2181 ;;
h|es|elasticsearch) run_grey h Elasticsearch opsxdb-elasticsearch elasticsearch 9200 ;;
i|influx|influxdb) run_grey i InfluxDB opsxdb-influxdb influxdb 8086 ;;
all|"")
run_sqlite
run_mysql
run_mariadb
run_postgres
run_redis
run_mssql
run_oracle
;;
*) usage; exit 1 ;;
esac
log
log "==== SUMMARY ok=$n_ok fail=$n_fail ===="
[[ "$n_fail" -eq 0 ]]