feat: 完成P6-P8后端能力与P9路由重构(V1-V2,D1,D3,B1-B2,B4,I1-I4,P9A2-P9A3)
这个提交包含在:
+146
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
BASE_URL="${BASE_URL:-http://127.0.0.1:3000}"
|
||||
DEVICE_FINGERPRINT="${DEVICE_FINGERPRINT:-regression-dashboard}"
|
||||
FINANCE_MGR_IDENTIFIER="${FINANCE_MGR_IDENTIFIER:-finance_mgr@hyinda.com}"
|
||||
FINANCE_MGR_PASSWORD="${FINANCE_MGR_PASSWORD:-FinanceMgrPassword1234}"
|
||||
DASHBOARD_PERF_CHECK="${DASHBOARD_PERF_CHECK:-0}"
|
||||
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
echo "[ERROR] curl not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v jq >/dev/null 2>&1; then
|
||||
echo "[ERROR] jq not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
cleanup() {
|
||||
rm -rf "$TMP_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
assert_status() {
|
||||
local actual="$1"
|
||||
local expected="$2"
|
||||
local name="$3"
|
||||
local body_file="${4:-}"
|
||||
|
||||
if [[ "$actual" != "$expected" ]]; then
|
||||
echo "[ERROR] $name expected HTTP $expected but got $actual" >&2
|
||||
if [[ -n "$body_file" && -f "$body_file" ]]; then
|
||||
cat "$body_file" >&2
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
login() {
|
||||
local out_file="$1"
|
||||
local status
|
||||
status="$(curl -sS -o "$out_file" -w "%{http_code}" \
|
||||
-X POST "$BASE_URL/api/auth/login" \
|
||||
-H "content-type: application/json" \
|
||||
-H "x-device-fingerprint: $DEVICE_FINGERPRINT" \
|
||||
-d "{\"identifier\":\"$FINANCE_MGR_IDENTIFIER\",\"password\":\"$FINANCE_MGR_PASSWORD\"}")"
|
||||
assert_status "$status" "200" "login" "$out_file"
|
||||
jq -er '.data.accessToken' "$out_file"
|
||||
}
|
||||
|
||||
api_json() {
|
||||
local method="$1"
|
||||
local url="$2"
|
||||
local token="$3"
|
||||
local out_file="$4"
|
||||
|
||||
curl -sS -o "$out_file" -w "%{http_code}" \
|
||||
-X "$method" "$url" \
|
||||
-H "authorization: Bearer $token"
|
||||
}
|
||||
|
||||
assert_json_shape() {
|
||||
local file="$1"
|
||||
local expr="$2"
|
||||
local name="$3"
|
||||
if ! jq -e "$expr" "$file" >/dev/null; then
|
||||
echo "[ERROR] $name json assertion failed: $expr" >&2
|
||||
cat "$file" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
echo "[1/5] Login"
|
||||
TOKEN="$(login "$TMP_DIR/login.json")"
|
||||
|
||||
FILTER_QS="dateFrom=2026-01-01&dateTo=2026-12-31"
|
||||
|
||||
echo "[2/5] Query dashboard endpoints"
|
||||
status="$(api_json GET "$BASE_URL/api/dashboard/expense-overview?$FILTER_QS" "$TOKEN" "$TMP_DIR/overview.json")"
|
||||
assert_status "$status" "200" "dashboard overview" "$TMP_DIR/overview.json"
|
||||
assert_json_shape "$TMP_DIR/overview.json" '.data.totalCount >= 0 and (.data.totalAmount | type == "string")' "overview"
|
||||
|
||||
status="$(api_json GET "$BASE_URL/api/dashboard/trends?$FILTER_QS" "$TOKEN" "$TMP_DIR/trends-1.json")"
|
||||
assert_status "$status" "200" "dashboard trends(1)" "$TMP_DIR/trends-1.json"
|
||||
assert_json_shape "$TMP_DIR/trends-1.json" '.data.points | type == "array"' "trends-array"
|
||||
assert_json_shape "$TMP_DIR/trends-1.json" '.data.points | all(.[]; (.date | type == "string") and (.amount | type == "string") and (.count | type == "number"))' "trends-shape"
|
||||
assert_json_shape "$TMP_DIR/trends-1.json" '
|
||||
.data.points
|
||||
| reduce .[] as $p ({prev:null, ok:true};
|
||||
if .ok == false then .
|
||||
elif .prev == null or $p.date >= .prev then {prev: $p.date, ok: true}
|
||||
else {prev: $p.date, ok: false}
|
||||
end
|
||||
)
|
||||
| .ok
|
||||
' "trends-order"
|
||||
|
||||
status="$(api_json GET "$BASE_URL/api/dashboard/trends?$FILTER_QS" "$TOKEN" "$TMP_DIR/trends-2.json")"
|
||||
assert_status "$status" "200" "dashboard trends(2)" "$TMP_DIR/trends-2.json"
|
||||
if ! diff -u <(jq -S '.data.points' "$TMP_DIR/trends-1.json") <(jq -S '.data.points' "$TMP_DIR/trends-2.json") >/dev/null; then
|
||||
echo "[ERROR] trends response not stable across repeated calls" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
status="$(api_json GET "$BASE_URL/api/dashboard/top-categories?$FILTER_QS&limit=5" "$TOKEN" "$TMP_DIR/top-categories.json")"
|
||||
assert_status "$status" "200" "dashboard top-categories" "$TMP_DIR/top-categories.json"
|
||||
assert_json_shape "$TMP_DIR/top-categories.json" '.data.items | type == "array" and (length <= 5)' "top-categories-array"
|
||||
assert_json_shape "$TMP_DIR/top-categories.json" '.data.items | all(.[]; (.code | type == "string") and (.name | type == "string") and (.amount | type == "string") and (.count | type == "number"))' "top-categories-shape"
|
||||
|
||||
status="$(api_json GET "$BASE_URL/api/dashboard/approval-efficiency?$FILTER_QS" "$TOKEN" "$TMP_DIR/efficiency.json")"
|
||||
assert_status "$status" "200" "dashboard approval-efficiency" "$TMP_DIR/efficiency.json"
|
||||
assert_json_shape "$TMP_DIR/efficiency.json" '.data.averageHours >= 0 and .data.approvedCount >= 0' "efficiency"
|
||||
|
||||
echo "[3/5] Export dashboard data"
|
||||
status="$(curl -sS -o "$TMP_DIR/export.csv" -w "%{http_code}" \
|
||||
-X GET "$BASE_URL/api/exports/dashboard/reimbursements?format=csv&$FILTER_QS" \
|
||||
-H "authorization: Bearer $TOKEN")"
|
||||
assert_status "$status" "200" "export dashboard csv" "$TMP_DIR/export.csv"
|
||||
if [[ ! -s "$TMP_DIR/export.csv" ]]; then
|
||||
echo "[ERROR] dashboard csv export is empty" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
status="$(curl -sS -o "$TMP_DIR/export.xlsx" -w "%{http_code}" \
|
||||
-X GET "$BASE_URL/api/exports/dashboard/reimbursements?format=xlsx&$FILTER_QS" \
|
||||
-H "authorization: Bearer $TOKEN")"
|
||||
assert_status "$status" "200" "export dashboard xlsx" "$TMP_DIR/export.xlsx"
|
||||
if [[ ! -s "$TMP_DIR/export.xlsx" ]]; then
|
||||
echo "[ERROR] dashboard xlsx export is empty" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$DASHBOARD_PERF_CHECK" == "1" ]]; then
|
||||
echo "[4/5] Optional performance probe"
|
||||
PERF_TIME="$(curl -sS -o /dev/null -w "%{time_total}" \
|
||||
-X GET "$BASE_URL/api/dashboard/trends?$FILTER_QS" \
|
||||
-H "authorization: Bearer $TOKEN")"
|
||||
echo "[INFO] trends endpoint time_total=${PERF_TIME}s"
|
||||
else
|
||||
echo "[4/5] Optional performance probe (skipped, DASHBOARD_PERF_CHECK=$DASHBOARD_PERF_CHECK)"
|
||||
fi
|
||||
|
||||
echo "[5/5] Done"
|
||||
echo "[OK] Dashboard regression passed"
|
||||
在新工单中引用
屏蔽一个用户