23e4472715
leo 2026-08-27:「開票不查是否有現成的,這是什麼問題?」「不只開票前, 所有新增 gitea 的東西,都要搜尋」。 v1 的 ticket-api-bypass-guard 只認裸字 `grep -qw POST`(大小寫敏感), 只管 issues 端點。2026-08-27 總管一天開 13 張票,v1 一次都沒攔到—— 用的是 `urllib.request.Request(url, data=...)`(靠傳 data= 隱式變 POST, 指令裡從頭到尾沒有 "POST" 三個字)和 `requests.post(...)`(小寫)。 本次改動: - 寫入訊號從單一裸字換成一組結構性訊號(curl 資料類旗標/`.post(`/ `method=post`/urllib 隱式 POST),且大小寫不敏感,同時保留 v1 的裸字比對 - 端點擴大到 milestones/labels/pulls(含 org 層級的 labels),不再只管 issues - 用「resource/數字」特徵先放行帶 ID 的既有資源子路徑(貼留言、改標籤、 合併 PR 等),避免貪婪比對把 `/issues/5/labels` 誤判成頂層 `/labels` 端點 - 擋下 issues 新增時,順手用標題猜關鍵字查一次跨 repo 搜尋,把可能撞到的 舊票(例如 Arcrun#100)直接列進擋下的訊息裡 測試:scripts/test-ticket-api-bypass-guard.sh 24/24 通過(v1 的 13 條 + 本次 新增 11 條,含一條打真實 Gitea 網路重演 Arcrun#100 重複主題偵測)。 plugin.json 0.5.0 → 0.5.1;version-consistency 會在合併打 tag 那一刻才對齊 (release-tag-guard.sh 的既定分工:打 tag 是總管驗過整個 milestone 之後的事)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
89 lines
5.2 KiB
Bash
Executable File
89 lines
5.2 KiB
Bash
Executable File
#!/bin/bash
|
||
# 開票側門閘的測試(docs/TESTING.md A4)
|
||
# 判準:4 種該擋、8 種不該擋、1 種有戳記時放行 = 13 條(v1,仍須全過,不能為了
|
||
# 新案例把舊的改壞)+ inkstone/ISEP#72 擴大範圍後新增的案例(隱式/小寫 POST、
|
||
# milestone/label/PR、org 端點、Arcrun#100 重演)。
|
||
cd "$(dirname "$0")/.." || exit 1
|
||
H=hooks/ticket-api-bypass-guard.sh
|
||
PASS=0; FAIL=0
|
||
run(){ # $1=want $2=cmd
|
||
printf '%s' "{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":$(python3 -c 'import json,sys;print(json.dumps(sys.argv[1]))' "$2")}}" \
|
||
| bash "$H" >/dev/null 2>&1
|
||
got=$?
|
||
if [ "$got" = "$1" ]; then PASS=$((PASS+1)); printf ' ✅ '; else FAIL=$((FAIL+1)); printf ' ❌ '; fi
|
||
printf 'want=%s got=%s %.56s\n' "$1" "$got" "$2"
|
||
}
|
||
SAVED=""; [ -f /tmp/.ticket-where-ok ] && SAVED=$(cat /tmp/.ticket-where-ok)
|
||
rm -f /tmp/.ticket-where-ok
|
||
|
||
echo "── 該擋(沒有搜尋戳記,且真的在開新票)──"
|
||
run 2 'curl -X POST https://git.uncle6.me/api/v1/repos/inkstone/ISEP/issues -d @b.json'
|
||
run 2 'python3 -c "req(\"POST\", f\"{API}/repos/{REPO}/issues\", {\"title\":\"x\"})"'
|
||
run 2 'curl --request POST "$API/repos/inkstone/InkStoneCo/issues"'
|
||
run 2 'req("POST",f"{API}/repos/{REPO}/issues",{"title":"x","labels":[1]})'
|
||
|
||
echo "── 不該擋(誤攔比漏擋更該修)──"
|
||
run 0 'curl -s "https://git.uncle6.me/api/v1/repos/inkstone/ISEP/issues?state=open"'
|
||
run 0 'req("POST", f"{API}/repos/{REPO}/issues/14/comments", {"body":"x"})'
|
||
run 0 'req("POST", f"{API}/repos/{REPO}/issues/5/labels", {"labels":[1]})'
|
||
run 0 'req("PATCH", f"{API}/repos/{REPO}/issues/5", {"state":"closed"})'
|
||
run 0 'scripts/ticket new ISEP -F /tmp/b.md --title "x"'
|
||
run 0 'echo "等一下要開票到 /repos/x/issues"'
|
||
run 0 'grep -n issues hooks/ticket-api-bypass-guard.sh'
|
||
run 0 'curl -s "$API/repos/inkstone/Arcrun/issues?state=open&limit=100"'
|
||
|
||
echo "── 有新鮮戳記時放行 ──"
|
||
python3 -c "import json,time;json.dump({'at':time.time(),'n':0,'top':[]},open('/tmp/.ticket-where-ok','w'))"
|
||
run 0 'curl -X POST https://git.uncle6.me/api/v1/repos/inkstone/ISEP/issues'
|
||
rm -f /tmp/.ticket-where-ok
|
||
|
||
echo
|
||
echo "── ISEP#72:2026-08-27 實際撞到的繞法(v1 grep -qw POST 漏掉的),該擋 ──"
|
||
run 2 'url = "https://git.uncle6.me/api/v1/repos/inkstone/ISEP/issues"; req = urllib.request.Request(url, data=json.dumps(payload).encode(), headers=h); urllib.request.urlopen(req)'
|
||
run 2 'python3 -c "requests.post(f\"{API}/repos/{REPO}/issues\", json={\"title\":\"x\"})"'
|
||
|
||
echo
|
||
echo "── ISEP#72:範圍擴大到 milestone/label/PR,該擋 ──"
|
||
run 2 'curl -X POST https://git.uncle6.me/api/v1/repos/inkstone/ISEP/milestones -d @m.json'
|
||
run 2 'curl -X POST https://git.uncle6.me/api/v1/repos/inkstone/ISEP/labels -d @l.json'
|
||
run 2 'curl -X POST https://git.uncle6.me/api/v1/repos/inkstone/ISEP/pulls -d @p.json'
|
||
run 2 'curl -X POST https://git.uncle6.me/api/v1/orgs/inkstone/labels -d @l.json'
|
||
|
||
echo
|
||
echo "── ISEP#72:帶 ID 的 milestone/label/PR 子路徑,不該擋(改狀態/合併,不是新增)──"
|
||
run 0 'curl -X PATCH https://git.uncle6.me/api/v1/repos/inkstone/ISEP/milestones/3 -d @m.json'
|
||
run 0 'curl -X PATCH https://git.uncle6.me/api/v1/repos/inkstone/ISEP/labels/9 -d @l.json'
|
||
run 0 'curl -X POST https://git.uncle6.me/api/v1/repos/inkstone/ISEP/pulls/12/merge'
|
||
|
||
echo
|
||
echo "── ISEP#72:有戳記時,milestone/label/PR 也放行(不是只有 issues)──"
|
||
python3 -c "import json,time;json.dump({'at':time.time(),'n':0,'top':[]},open('/tmp/.ticket-where-ok','w'))"
|
||
run 0 'curl -X POST https://git.uncle6.me/api/v1/repos/inkstone/ISEP/milestones -d @m.json'
|
||
rm -f /tmp/.ticket-where-ok
|
||
|
||
echo
|
||
echo "── ISEP#72:Arcrun#100 重演——標題撞同主題舊票,擋下來還要點名 ──"
|
||
echo " (這條打真實 Gitea 網路;離線環境會印 SKIP,不算失敗。標題故意用"
|
||
echo " 裸引號的 python dict 寫法,跟 2026-08-27 那 13 張票實際的寫法一致——"
|
||
echo " curl -d 那種「JSON 被跳脫成字串塞進另一層引號」的寫法,本閘的猜題功能"
|
||
echo " 刻意不支援,見 hook 檔頭「只認雙引號」那段註解,訊息裡不會列候選舊票,"
|
||
echo " 但擋下來這件事不受影響,另有獨立案例覆蓋。)"
|
||
if git remote -v 2>/dev/null | grep 'git\.uncle6\.me' | grep -q '@'; then
|
||
OUT=$(printf '%s' "{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":$(python3 -c 'import json,sys;print(json.dumps(sys.argv[1]))' 'req("POST", f"{API}/repos/{REPO}/issues", {"title":"總圖說我一條關聯都沒有,三元組 graph 全部消失"})')}}" \
|
||
| bash "$H" 2>&1 >/dev/null)
|
||
RC=$?
|
||
if [ "$RC" = 2 ] && printf '%s' "$OUT" | grep -q 'Arcrun#100'; then
|
||
PASS=$((PASS+1)); echo " ✅ 擋下來(rc=2)且點名 Arcrun#100"
|
||
else
|
||
FAIL=$((FAIL+1)); echo " ❌ rc=$RC,訊息裡有沒有 Arcrun#100:"; printf '%s\n' "$OUT" | grep -q 'Arcrun#100' && echo "有" || echo "沒有"
|
||
printf '%s\n' "$OUT"
|
||
fi
|
||
else
|
||
echo " ⏭️ SKIP(這台機器的 git remote 沒帶 git.uncle6.me 的憑證,連不上就不算數)"
|
||
fi
|
||
|
||
[ -n "$SAVED" ] && printf '%s' "$SAVED" > /tmp/.ticket-where-ok
|
||
echo
|
||
echo "$PASS/$((PASS+FAIL)) 通過"
|
||
[ "$FAIL" -eq 0 ]
|