单测打的是假 HTTP 夹具,Web 端测试只验自己那一侧 —— 两端各自绿灯不代表契约字段
真的对得上。本版加了跨端集成验收,并在首次运行就抓到两个真实缺陷。
一、跨端集成验收(T-WIN-019)
* build/integration/probe-integration.gd:只加载**包内**代码
(`godot --main-pack <pck> --script <探针>` + res://),打真实服务端并逐项断言:
/health 六个契约版本、catalog/version 指纹、catalog/export 解析出的四类资源与
defaultModelId;**场景物理参数逐项比对**(重力/基础风/阵风/风坐标系/地面摩擦坡度/
碰撞体/出生点/相机/环境)+ Wind.sample(0) + 场景 GLB 可下载;
动作 NDJSON → 客户端解析帧数 == 服务端帧数、33 点、每帧服务端关节角、
推导帧率 30 fps、时长 96.67 s;缩略图 PNG;模型 VRM 可下载。
* build/integration/seed-catalog.mjs:用**真实素材**播种目录
(示例文件/ 下的真 VMD → 内部标准 33 点;真 PMX → VRM;场景 GLB + 物理参数),
并把期望值(含从 /health 取的契约版本)写成 JSON。
* build/accept-integration.sh:编排「服务端可达 → 播种 → 包内探针 → 汇报」。
实测(v0.2.3 包内 pck × 真实 Web v0.7.4 + 真实 MySQL):全部通过。
二、探针抓到的两个真实缺陷
1. `CatalogTypes.Motion` 缺 `thumbnail_url`:服务端 catalog/export 从
data-models 0.6.0 起一直在发 thumbnailUrl(Model/Scene 都有),客户端默默丢掉。
已补字段与解析(缺失为 ""),新增 tests/test_catalog_types.gd(4 项 20 断言)
用**服务端真实报文形状**锁住三个资源类型的覆盖,并显式记录一处刻意不消费的差异
(Model.usable/unusableReason —— 目录只下发可用模型,恒为 true)。
2. `ApiClient._request()` 对**所有**响应无条件 `raw.get_string_from_utf8()`:
数据本身没错(二进制路径返回原始字节),但每下载一个文件都会打印
「Unicode parsing error … Invalid UTF-8 leading byte (N)」噪声,并把整个文件
(模型可达数十 MB)解码成 String 拷贝后丢弃。改为只在需要文本时解码。
三、顺带修掉测试里的写死契约版本
tests/test_api_integration.gd 断言 rest-api/websocket-keypoints/skeleton-topology
均为 "0.1.0"。该用例只在服务端可达时才跑,平时一直 skip,所以契约升到 0.5.0 后
没有任何地方发现它。改为从仓库 contracts/VERSION 读期望值并与 /health 比对
(顺带成为「客户端契约快照 vs 服务端」的跨端校验)。
证据:verify.sh 全绿(820 通过 / 0 失败,比上版 +46,因为服务端在线时 API 集成组
真的跑了);包内 pck 跨端集成探针全部通过;check-version/PE 版本资源 = 0.2.3。
136 lines
5.6 KiB
Bash
Executable File
136 lines
5.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# ============================================================
|
||
# OpenDance Studio Windows 客户端 —— **跨端集成验收**(T-WIN-019)
|
||
#
|
||
# Windows 发布包里的客户端(包内 pck) × 真实 Web 服务端
|
||
#
|
||
# 为什么需要它:本仓单测打的是**假 HTTP 夹具**,Web 端测试只验自己那一侧。
|
||
# 两端各自绿灯 ≠ 契约字段真的对得上(字段名/嵌套/null/单位/范围都可能一侧对一侧错)。
|
||
# 契约 0.5.0 新增的 `scenes[]` 与重力/风向参数尤其如此。
|
||
#
|
||
# 本脚本:
|
||
# 1. 要求一个**已部署好的** Web 服务端(不做部署,与项目边界一致);
|
||
# 2. 用真实素材播种一份目录(真 VMD → 内部标准;真 PMX → VRM;场景 GLB + 物理参数),
|
||
# 并写出期望值 JSON(`--skip-seed` 可跳过,复用已有目录);
|
||
# 3. 用 Godot 加载**发布包里的 pck**(`--main-pack`)跑
|
||
# `build/integration/probe-integration.gd`,逐项断言并给出退出码。
|
||
#
|
||
# 用法:
|
||
# GODOT_BIN=/path/to/godot API_BASE=http://127.0.0.1:3000 \
|
||
# bash build/accept-integration.sh dist-release/opendance-windows-v0.2.3.zip
|
||
#
|
||
# # 已播种过、只重跑探针:
|
||
# GODOT_BIN=... API_BASE=... bash build/accept-integration.sh <zip> --skip-seed
|
||
#
|
||
# 环境变量:
|
||
# API_BASE Web 服务端基地址(默认 http://127.0.0.1:3000)
|
||
# OPENDANCE_SAMPLES_DIR 真实素材目录(默认 仓库根/示例文件)
|
||
# EXPECT_PATH 期望值 JSON 输出路径(默认 $WORK_DIR/catalog-expect.json)
|
||
# WORK_DIR 解包目录(默认 mktemp -d)
|
||
# ============================================================
|
||
set -uo pipefail
|
||
|
||
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||
cd "$PROJECT_DIR"
|
||
|
||
TARGET="${1:-}"
|
||
SKIP_SEED=0
|
||
for arg in "$@"; do [ "$arg" = "--skip-seed" ] && SKIP_SEED=1; done
|
||
|
||
API_BASE="${API_BASE:-http://127.0.0.1:3000}"
|
||
SAMPLES_DIR="${OPENDANCE_SAMPLES_DIR:-$PROJECT_DIR/../示例文件}"
|
||
WORK_DIR="${WORK_DIR:-$(mktemp -d)}"
|
||
EXPECT_PATH="${EXPECT_PATH:-$WORK_DIR/catalog-expect.json}"
|
||
|
||
FAILED=0
|
||
log() { printf '\033[1;34m[accept-int]\033[0m %s\n' "$*"; }
|
||
fail() { printf '\033[1;31m[accept-int] ✗ %s\033[0m\n' "$*" >&2; FAILED=1; }
|
||
|
||
if [ -z "$TARGET" ] || [ ! -e "$TARGET" ]; then
|
||
echo "用法:GODOT_BIN=... API_BASE=... bash build/accept-integration.sh <发布包 zip 或已解压目录> [--skip-seed]" >&2
|
||
exit 2
|
||
fi
|
||
# 必须**先**建目录:期望值 JSON 默认就落在 $WORK_DIR 下,播种阶段就要写它
|
||
mkdir -p "$WORK_DIR"
|
||
|
||
# ---- 0. 服务端可达性 ----
|
||
log "服务端:$API_BASE"
|
||
HEALTH="$(curl -sf -m 10 "$API_BASE/health" || true)"
|
||
if [ -z "$HEALTH" ]; then
|
||
fail "GET $API_BASE/health 不可达 —— 请先部署 Web 服务端(本项目不部署它)"
|
||
exit 1
|
||
fi
|
||
echo "$HEALTH" | node -e '
|
||
let s = ""; process.stdin.on("data", (d) => (s += d)).on("end", () => {
|
||
const j = JSON.parse(s);
|
||
console.log(` ✓ 服务端版本 ${j.version}|数据库 ${j.database?.connected ? "已连接" : "未连接"}|scenes.available=${j.scenes?.available}`);
|
||
});'
|
||
|
||
# ---- 1. 播种目录 ----
|
||
if [ "$SKIP_SEED" = "1" ]; then
|
||
log "跳过播种(--skip-seed);期望值文件:$EXPECT_PATH"
|
||
else
|
||
log "播种真实目录(真 VMD / 真 PMX / 场景 GLB + 物理参数)…"
|
||
if [ ! -d "$SAMPLES_DIR" ]; then
|
||
fail "找不到素材目录 $SAMPLES_DIR(用 OPENDANCE_SAMPLES_DIR 指定)"
|
||
exit 1
|
||
fi
|
||
if ! command -v node >/dev/null 2>&1; then
|
||
fail "播种需要 node(Web 端已依赖 node,本机应已安装)"
|
||
exit 1
|
||
fi
|
||
API_BASE="$API_BASE" OPENDANCE_SAMPLES_DIR="$SAMPLES_DIR" EXPECT_PATH="$EXPECT_PATH" \
|
||
node "$PROJECT_DIR/build/integration/seed-catalog.mjs" || { fail "播种失败"; exit 1; }
|
||
fi
|
||
if [ ! -f "$EXPECT_PATH" ]; then
|
||
fail "缺少期望值文件 $EXPECT_PATH(首次运行不要加 --skip-seed)"
|
||
exit 1
|
||
fi
|
||
|
||
# ---- 2. 取包内 pck ----
|
||
if [ -f "$TARGET" ]; then
|
||
log "解压发布包…"
|
||
if ! unzip -qq -o "$TARGET" -d "$WORK_DIR" 2>/dev/null; then
|
||
command -v 7z >/dev/null 2>&1 && 7z x -y -o"$WORK_DIR" "$TARGET" >/dev/null || { fail "解压失败"; exit 1; }
|
||
fi
|
||
PACKAGE_DIR="$(find "$WORK_DIR" -maxdepth 1 -mindepth 1 -type d | head -1)"
|
||
else
|
||
PACKAGE_DIR="$TARGET"
|
||
fi
|
||
PCK="$PACKAGE_DIR/OpenDance.pck"
|
||
[ -f "$PCK" ] || { fail "找不到包内 pck:$PCK"; exit 1; }
|
||
log "包内 pck:$PCK($(stat -c%s "$PCK") 字节)"
|
||
|
||
# ---- 3. Godot ----
|
||
GODOT_BIN="${GODOT_BIN:-}"
|
||
if [ -z "$GODOT_BIN" ]; then
|
||
for candidate in godot godot4 Godot; do
|
||
command -v "$candidate" >/dev/null 2>&1 && { GODOT_BIN="$candidate"; break; }
|
||
done
|
||
fi
|
||
[ -n "$GODOT_BIN" ] || { fail "未找到 Godot(用 GODOT_BIN=<路径> 指定,需与本项目同版本 4.7.x)"; exit 1; }
|
||
|
||
# ---- 4. 跨端探针 ----
|
||
log "跨端集成探针(包内客户端 × $API_BASE)…"
|
||
PROBE_LOG="$WORK_DIR/probe-integration.log"
|
||
if timeout 600 "$GODOT_BIN" --headless --main-pack "$PCK" \
|
||
--script "$PROJECT_DIR/build/integration/probe-integration.gd" \
|
||
-- --api-base "$API_BASE" --expect "$EXPECT_PATH" >"$PROBE_LOG" 2>&1; then
|
||
PROBE_OK=1
|
||
else
|
||
PROBE_OK=0
|
||
fi
|
||
grep -vE "^Godot Engine|^$" "$PROBE_LOG" | sed 's/^/ /'
|
||
[ "$PROBE_OK" = 1 ] || fail "跨端集成探针未通过"
|
||
|
||
echo ""
|
||
if [ "$FAILED" -ne 0 ]; then
|
||
echo "============================================================"
|
||
echo " 跨端集成验收失败(工作目录:$WORK_DIR)"
|
||
echo "============================================================"
|
||
exit 1
|
||
fi
|
||
echo "============================================================"
|
||
echo " 跨端集成验收全部通过(工作目录:$WORK_DIR)"
|
||
echo "============================================================"
|