From c6eb2695519dc803c5d6c07d7d07bf7e0d673d33 Mon Sep 17 00:00:00 2001 From: claude-code Date: Fri, 28 Aug 2026 01:04:20 +0000 Subject: [PATCH] =?UTF-8?q?=E8=B7=A8=20repo=20=E7=89=88=E6=9C=AC=E6=B8=85?= =?UTF-8?q?=E5=96=AE=EF=BC=8B=E7=B5=90=E6=A1=88=E8=A8=98=E5=B8=B3=EF=BC=88?= =?UTF-8?q?inkstone/ISEP#84=E3=80=81#85=EF=BC=89=EF=BC=88scripts/test-mile?= =?UTF-8?q?stone-account.sh=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/test-milestone-account.sh | 155 ++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 scripts/test-milestone-account.sh diff --git a/scripts/test-milestone-account.sh b/scripts/test-milestone-account.sh new file mode 100644 index 0000000..7418c25 --- /dev/null +++ b/scripts/test-milestone-account.sh @@ -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 ]