跨 repo 版本清單+結案記帳(inkstone/ISEP#84、#85)(scripts/test-milestone-account.sh)
This commit is contained in:
@@ -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 ]
|
||||
Reference in New Issue
Block a user