跨 repo 版本清單+結案記帳(inkstone/ISEP#84、#85)(scripts/test-milestone-account.sh)

This commit is contained in:
2026-08-28 01:04:20 +00:00
parent bc02c2645b
commit c6eb269551
+155
View File
@@ -0,0 +1,155 @@
#!/usr/bin/env bash
# scripts/milestone-account 的迴歸測試(inkstone/ISEP#85
#
# 驗收 1:關掉一個 milestone → 三個數字要自己算出來,不用手填
# 驗收 2:差超過 25% → 要逼我挑一個代號,挑不出來不准結案
# 驗收 3:累積幾次之後 → 看得出哪一種原因最常發生
# 驗收 4:拿已經逾期的 milestone 進去算 → 要算得出數字
#
# 🔴 全程離線(MILESTONE_ACCOUNT_FIXTURE =一份假的 Gitea 回應表),
# 時鐘用 MILESTONE_ACCOUNT_NOW 定住,帳本寫在 mktemp 目錄。
# **不打 Gitea、不關任何真的 milestone、不碰 repo 裡的 system-dev/estimates/。**
set -u
TOOL="${1:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/milestone-account}"
TMP=$(mktemp -d); trap 'rm -rf "$TMP"' EXIT
PASS=0; FAIL=0; N=0
NOW="2026-08-28T00:00:00Z"
FX="$TMP/fx.json"
python3 - "$FX" <<'PY'
import json, sys, urllib.parse
api = {}
def iso(d, h=0):
return "2026-08-%02dT%02d:00:00Z" % (d, h)
def issue(n, created, closed=None, assignee=True, labels=(), ms=None):
return {"number": n, "created_at": created, "closed_at": closed,
"updated_at": closed or created,
"assignees": [{"login": "cc"}] if assignee else [],
"labels": [{"name": l} for l in labels],
"milestone": ({"id": ms} if ms else None)}
def comment(n, at):
return {"issue_url": "https://x/api/v1/repos/o/r/issues/%d" % n, "created_at": at}
def repo(full, mid, title, created, due, closed, issues, comments, open_ms=True):
o, r = full.split("/")
ms = {"id": mid, "title": title, "created_at": created, "due_on": due,
"closed_at": closed, "state": "open" if closed is None else "closed"}
api["/repos/%s/%s/milestones/%d" % (o, r, mid)] = ms
api["/repos/%s/%s/issues?state=all&type=issues&limit=50&page=1" % (o, r)] = issues
api["/repos/%s/%s/issues/comments?limit=100&since=%s"
% (o, r, urllib.parse.quote(created))] = comments
api["/repos/%s/%s/milestones?state=open&limit=50" % (o, r)] = [ms] if open_ms else []
# ① A#12 磨洋工:08-02 收掉一張之後,時間軸整整空了 24 天
repo("inkstone/A", 12, "sprint-磨洋工", iso(1), "2026-08-08T23:59:59Z", None,
[issue(1, iso(1, 1), iso(2), ms=12), issue(2, iso(1, 2), ms=12),
issue(3, iso(1, 3), ms=12), issue(9, iso(10), ms=None)],
[comment(1, iso(26))])
# ② B#13 準時收掉:偏差只有 -6%,不必挑代號
repo("inkstone/B", 13, "sprint-準時", iso(20), "2026-08-27T23:59:59Z",
"2026-08-27T12:00:00Z",
[issue(1, iso(20, 1), iso(25), ms=13), issue(2, iso(20, 2), iso(26), ms=13)],
[comment(1, iso(22)), comment(2, iso(24))], open_ms=False)
# ③ C#14 過程沒異常、就是估錯:每兩天都有動作,其他六個訊號全不成立
repo("inkstone/C", 14, "sprint-估錯", iso(1), "2026-08-10T23:59:59Z",
"2026-08-25T00:00:00Z",
[issue(1, iso(1, 1), iso(24), ms=14), issue(2, iso(1, 2), iso(23), ms=14)],
[comment(1, iso(d)) for d in range(3, 25, 2)], open_ms=False)
# ④ D#15 範圍變大:開跑之後又補進來三張票
repo("inkstone/D", 15, "sprint-範圍變大", iso(1), "2026-08-10T23:59:59Z",
"2026-08-25T00:00:00Z",
[issue(1, iso(1, 1), iso(24), ms=15), issue(2, iso(1, 2), iso(23), ms=15),
issue(3, iso(15), ms=15), issue(4, iso(15, 1), ms=15),
issue(5, iso(15, 2), ms=15)],
[comment(1, iso(d)) for d in range(3, 25, 2)], open_ms=False)
api["/orgs/inkstone/repos?limit=100"] = [
{"full_name": "inkstone/%s" % x} for x in "ABCD"]
json.dump({"api": api}, open(sys.argv[1], "w"), ensure_ascii=False)
PY
run() { MILESTONE_ACCOUNT_FIXTURE="$FX" MILESTONE_ACCOUNT_DIR="$TMP/led" \
MILESTONE_ACCOUNT_NOW="$NOW" python3 "$TOOL" "$@" 2>&1; }
t() {
want="$1"; desc="$2"; must="$3"; shift 4
N=$((N+1)); out=$(run "$@"); rc=$?
ok=1; [ "$rc" -eq "$want" ] || ok=0
if [ -n "$must" ] && ! printf '%s' "$out" | grep -qF "$must"; then ok=0; fi
if [ "$ok" -eq 1 ]; then printf ' ✅ %s\n' "$desc"; PASS=$((PASS+1))
else
printf ' ❌ %s —— 期望 exit=%s%s,實得 exit=%s\n' "$desc" "$want" \
"${must:+ 且輸出含「$must}" "$rc"
printf '%s\n' "$out" | sed -n '1,12p' | sed 's/^/ /'; FAIL=$((FAIL+1))
fi
}
echo "── 驗收 1:三個數字自己算出來 ─────────────────────────────"
t 0 "① audit 印出「當初訂的期限」" "當初訂的期限" -- audit inkstone/A#12
t 0 "② audit 印出「實際花掉的」" "實際花掉的" -- audit inkstone/A#12
t 0 "③ 偏差算得出來(A#12 逾期 +238%" "+238%" -- audit inkstone/A#12
t 0 "④ 期限是 08-08、實際算到今天 08-28 ⇒ 27 天" "27.0天" -- audit inkstone/A#12
echo "── 驗收 2:差超過 25% 逼你挑代號 ──────────────────────────"
t 2 "⑤ A#12 差 +237% 卻沒給 --reason ⇒ 不准結案" "挑不出代號不准結案" -- \
close inkstone/A#12
t 2 "⑥ 承上:訊息要直接列出撐得住的代號" "時間軸撐得住的代號:idle" -- close inkstone/A#12
t 0 "⑦ B#13 只差 -6% ⇒ 不必挑代號,直接結得掉" "已記帳" -- close inkstone/B#13
t 2 "⑧ 造一個不存在的代號 ⇒ 擋(封閉集合)" "不是那七個代號之一" -- \
close inkstone/A#12 --reason 拖延症
t 2 "⑨ 挑一個時間軸撐不住的代號 ⇒ 擋(不採信我自己說的)" "時間軸撐不住" -- \
close inkstone/C#14 --reason idle
t 0 "⑩ C#14 六個訊號都不成立 ⇒ 唯一合法的是 misestimate" "misestimate" -- \
audit inkstone/C#14
t 0 "⑪ 承上:--reason misestimate 收得掉" "原因=misestimate" -- \
close inkstone/C#14 --reason misestimate
t 0 "⑫ A#12 挑 idle(時間軸真的有 24 天空白)⇒ 收得掉" "原因=idle" -- \
close inkstone/A#12 --reason idle
t 0 "⑬ D#15 開跑後補三張票 ⇒ scope-creep 撐得住" "原因=scope-creep" -- \
close inkstone/D#15 --reason scope-creep
echo "── 驗收 3:看得出哪一種原因最常發生 ───────────────────────"
t 0 "⑭ report 數得出每個代號各幾次" "磨洋工" -- report
t 0 "⑮ report 算得出累計結案幾個" "累計結案 4 個里程碑" -- report
N=$((N+1))
if grep -q "哪一種原因最常發生" "$TMP/led/REPORT.md" 2>/dev/null \
&& grep -q "時間軸證據" "$TMP/led/REPORT.md"; then
printf ' ✅ ⑯ REPORT.md 寫出來了,而且每一筆都帶時間軸證據\n'; PASS=$((PASS+1))
else
printf ' ❌ ⑯ REPORT.md 內容不對\n'; FAIL=$((FAIL+1))
fi
N=$((N+1))
if [ "$(wc -l < "$TMP/led/ledger.jsonl")" = "4" ]; then
printf ' ✅ ⑰ 帳本剛好四筆(沒有被擋下的那幾次也記進去)\n'; PASS=$((PASS+1))
else
printf ' ❌ ⑰ 帳本筆數不對:%s\n' "$(wc -l < "$TMP/led/ledger.jsonl" 2>/dev/null)"
FAIL=$((FAIL+1))
fi
echo "── 驗收 4:拿已經逾期的里程碑進去算 ───────────────────────"
t 0 "⑱ list --overdue 撈得到逾期的那一個,而且帶數字" "inkstone/A#12" -- \
list --org inkstone --overdue
t 0 "⑲ 承上:逾期的要被標成「差超過 ±25%」那一類" "🔴" -- \
list --org inkstone --overdue
echo "── 其他 ───────────────────────────────────────────────────"
t 0 "⑳ codes 印得出七個代號,一個不多一個不少" "misestimate" -- codes
N=$((N+1))
if [ "$(run codes | grep -cE '^ [a-z-]+ ')" = "7" ]; then
printf ' ✅ ㉑ 真的是七個(封閉集合)\n'; PASS=$((PASS+1))
else
printf ' ❌ ㉑ 代號數量不是 7%s\n' "$(run codes | grep -cE '^ [a-z-]+ ')"; FAIL=$((FAIL+1))
fi
t 0 "㉒ --dry-run 印出要打的那通 API 但不送出" "一筆都沒有真的寫出去" -- \
close inkstone/A#12 --reason idle --dry-run
t 2 "㉓ 里程碑編號寫錯 ⇒ 講清楚寫法" "寫法是 owner/repo#里程碑編號" -- audit ISEP-12
echo
echo "通過 $PASS 條,失敗 $FAIL 條(共 $N 條)"
[ "$FAIL" -eq 0 ]