summaryrefslogtreecommitdiff
path: root/qa/lifecycle-test.sh
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-21 02:23:43 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-21 02:23:43 +0800
commitf55ed58d2c75b53774ef7256fddb973ae3e73323 (patch)
tree63269f233f4305d5b68aa252e9548244a362adcf /qa/lifecycle-test.sh
parentbe1afd248e4cc9b03a1e21090602a945af6988b2 (diff)
downloadfund-lab-f55ed58d2c75b53774ef7256fddb973ae3e73323.tar.gz
feat(web): 基金创建/读取界面与仓库内浏览器 QA
[变更性质] - 本提交完成 3c-1 切片:Web 端基金建档(创建/重读)表单接入既有幂等 API,并提供仓库内可复现的端到端 QA 入口与生命周期回归。 [新增功能] - 前端新增 03 / CREATE 面板:名称与初始现金(decimal 字符串)输入、创建/重新读取按钮、服务端返回的基金档案行(份额诚实显示 0、来源标注真实建档/合成标记);移动端单列布局。 - 创建请求携带 32 位十六进制 Idempotency-Key;网络失败重试复用同一 key,输入变更或成功后换新 key;token 变更清空档案并按序列号拒绝过期响应。 [实现方案] - 金额全程 decimal 字符串,不经 JS float;update 层 fundSeq 守卫 FundCreate/FundRead 完成消息;BoundaryTests 新增 FundBoundaryTests 9 项(幂等键长度/复用、token 失效、stale 读取拒绝等)。 - qa/run.sh 一次性拉起非默认端口 Postgres/API/Vite(桩行情,_synthetic 标记),清理仅限本次创建的容器(ID+名称归属校验);临时目录 mktemp 隔离;qa/lifecycle-test.sh 验证拒绝路径不破坏既有资源。 [影响范围] - Web 测试增至 17 项,全套 56 项通过;浏览器 QA 30/30 通过;README 更新阶段描述。本提交不包含 docs/overnight-progress.md 的现有修改。
Diffstat (limited to 'qa/lifecycle-test.sh')
-rwxr-xr-xqa/lifecycle-test.sh64
1 files changed, 64 insertions, 0 deletions
diff --git a/qa/lifecycle-test.sh b/qa/lifecycle-test.sh
new file mode 100755
index 0000000..ace6a08
--- /dev/null
+++ b/qa/lifecycle-test.sh
@@ -0,0 +1,64 @@
+#!/usr/bin/env bash
+# Lifecycle regression for qa/run.sh refusal paths.
+# Verifies that run.sh refuses to start when a container name or port is
+# already taken, and that the refusal leaves the pre-existing resources
+# untouched (no container deletion, no killing of the port holder).
+# The script only manages resources it created itself.
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+PG_CONTAINER=fund-lab-qa-pg-3c1
+PG_IMAGE=postgres:16-alpine
+API_PORT=5095
+TMP="$(mktemp -d /tmp/opencode/fund-lab-qa-lifecycle-XXXXXX)"
+HOLDER_PID=""
+
+cleanup() {
+ local status=$?
+ trap - EXIT INT TERM
+ [ -n "$HOLDER_PID" ] && kill "$HOLDER_PID" 2>/dev/null || true
+ [ -n "$HOLDER_PID" ] && wait "$HOLDER_PID" 2>/dev/null || true
+ if [ -n "$DUMMY_ID" ]; then
+ owned="$(docker ps -a --filter "id=$DUMMY_ID" --format '{{.Names}}' 2>/dev/null || true)"
+ [ "$owned" = "$PG_CONTAINER" ] && docker rm -f "$DUMMY_ID" >/dev/null 2>&1 || true
+ fi
+ rm -rf "$TMP"
+ exit "$status"
+}
+DUMMY_ID=""
+trap cleanup EXIT INT TERM
+
+log() { printf '[lifecycle] %s\n' "$*"; }
+fail() { printf '[lifecycle] FAIL: %s\n' "$*" >&2; exit 1; }
+pass() { printf '[lifecycle] PASS: %s\n' "$*"; }
+
+command -v docker >/dev/null || fail "docker is required"
+docker info >/dev/null 2>&1 || fail "docker daemon is not reachable"
+docker image inspect "$PG_IMAGE" >/dev/null 2>&1 || fail "$PG_IMAGE is not available locally"
+docker ps -a --format '{{.Names}}' | grep -qx "$PG_CONTAINER" && fail "container $PG_CONTAINER already exists; refusing to proceed"
+
+log "scenario 1: pre-existing container name is refused and preserved"
+DUMMY_ID="$(docker run -d --name "$PG_CONTAINER" "$PG_IMAGE" sleep 120)"
+if bash "$ROOT/qa/run.sh" >"$TMP/refuse-container.log" 2>&1; then
+ fail "run.sh should refuse when $PG_CONTAINER already exists"
+fi
+grep -q "already exists" "$TMP/refuse-container.log" || fail "refusal reason missing from log"
+[ "$(docker ps -a --filter "id=$DUMMY_ID" --format '{{.Names}}' 2>/dev/null)" = "$PG_CONTAINER" ] \
+ || fail "pre-existing container was removed or modified by the refused run"
+pass "refusal preserved the pre-existing container"
+docker rm -f "$DUMMY_ID" >/dev/null
+DUMMY_ID=""
+
+log "scenario 2: occupied API port is refused and the holder is untouched"
+python3 -m http.server "$API_PORT" --bind 127.0.0.1 >/dev/null 2>&1 &
+HOLDER_PID=$!
+sleep 0.5
+kill -0 "$HOLDER_PID" 2>/dev/null || fail "port holder failed to start"
+if bash "$ROOT/qa/run.sh" >"$TMP/refuse-port.log" 2>&1; then
+ fail "run.sh should refuse when port $API_PORT is occupied"
+fi
+grep -q "port $API_PORT is already in use" "$TMP/refuse-port.log" || fail "refusal reason missing from log"
+kill -0 "$HOLDER_PID" 2>/dev/null || fail "port holder was killed by the refused run"
+pass "refusal preserved the port holder"
+
+log "all lifecycle scenarios passed"