test(清理成品): 新增 tag 清理測試並抽出共用 jq mock

新增 entrypoint_tag_cleanup.sh 驗證孤兒 tag 刪除、保留指定 release 的 tag;分頁測試改為端點感知並共用 jq mock。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jeffery
2026-06-23 16:14:56 +08:00
co-authored by Claude Opus 4.8
parent 30f5dce8dc
commit 6cad836ce3
3 changed files with 222 additions and 79 deletions
+20 -79
View File
@@ -1,6 +1,9 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -Eeuo pipefail set -Eeuo pipefail
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(cd "$script_dir/.." && pwd)"
tmpdir="$(mktemp -d)" tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT trap 'rm -rf "$tmpdir"' EXIT
@@ -22,14 +25,20 @@ if [[ " $* " == *" -X DELETE "* ]]; then
exit 0 exit 0
fi fi
# tags 端點沒有需要清理的 tag
if [[ "$last_arg" == */tags?* ]]; then
printf '%s' '[]'
exit 0
fi
case "$last_arg" in case "$last_arg" in
*page=1) */releases?*page=1)
printf '%s' '[{"id":4,"tag_name":"v4","name":"release-4","created_at":"2024-04-01T00:00:00Z"},{"id":3,"tag_name":"v3","name":"release-3","created_at":"2024-03-01T00:00:00Z"}]' printf '%s' '[{"id":4,"tag_name":"v4","name":"release-4","created_at":"2024-04-01T00:00:00Z"},{"id":3,"tag_name":"v3","name":"release-3","created_at":"2024-03-01T00:00:00Z"}]'
;; ;;
*page=2) */releases?*page=2)
printf '%s' '[{"id":2,"tag_name":"v2","name":"release-2","created_at":"2024-02-01T00:00:00Z"},{"id":1,"tag_name":"v1","name":"release-1","created_at":"2024-01-01T00:00:00Z"}]' printf '%s' '[{"id":2,"tag_name":"v2","name":"release-2","created_at":"2024-02-01T00:00:00Z"},{"id":1,"tag_name":"v1","name":"release-1","created_at":"2024-01-01T00:00:00Z"}]'
;; ;;
*page=3) */releases?*page=3)
printf '%s' '[]' printf '%s' '[]'
;; ;;
*) *)
@@ -40,80 +49,7 @@ esac
EOF EOF
chmod +x "$bin_dir/curl" chmod +x "$bin_dir/curl"
cat >"$bin_dir/jq.py" <<'PY' cp "$script_dir/lib/jq_mock.py" "$bin_dir/jq.py"
#!/usr/bin/env python3
import json
import re
import sys
expr = ""
flags = set()
args = sys.argv[1:]
while args:
current = args.pop(0)
if current in {"-e", "-c", "-r", "-s"}:
flags.add(current)
continue
if current == "--":
if not args:
raise SystemExit("missing jq expression")
expr = args.pop(0)
break
if current.startswith("-"):
flags.add(current)
continue
expr = current
break
raw = sys.stdin.read()
def dump(value):
if "-r" in flags and isinstance(value, (str, int, float)) and not isinstance(value, bool):
sys.stdout.write(str(value))
else:
sys.stdout.write(json.dumps(value, separators=(",", ":")))
if expr == "length":
data = json.loads(raw or "null")
print(len(data))
raise SystemExit(0)
if expr == "add":
arrays = [json.loads(line) for line in raw.splitlines() if line.strip()]
merged = []
for item in arrays:
merged.extend(item)
dump(merged)
raise SystemExit(0)
if expr == "sort_by(.created_at) | reverse":
data = json.loads(raw or "[]")
dump(sorted(data, key=lambda item: item["created_at"], reverse=True))
raise SystemExit(0)
match = re.fullmatch(r"\.\[(\d+):\]", expr)
if match:
data = json.loads(raw or "[]")
dump(data[int(match.group(1)):])
raise SystemExit(0)
if expr == ".[]":
data = json.loads(raw or "[]")
for item in data:
dump(item)
sys.stdout.write("\n")
raise SystemExit(0)
match = re.fullmatch(r"\.(id|tag_name|name)", expr)
if match:
data = json.loads(raw or "null")
value = data.get(match.group(1))
dump(value)
raise SystemExit(0)
raise SystemExit(f"unsupported jq expression: {expr}")
PY
chmod +x "$bin_dir/jq.py" chmod +x "$bin_dir/jq.py"
cat >"$bin_dir/jq" <<'EOF' cat >"$bin_dir/jq" <<'EOF'
@@ -129,7 +65,7 @@ GITEA_SERVER_URL="https://gitea.example.test" \
GITEA_REPOSITORY="owner/repo" \ GITEA_REPOSITORY="owner/repo" \
KEEP_COUNT="2" \ KEEP_COUNT="2" \
RUNNER_TOKEN="" \ RUNNER_TOKEN="" \
bash ./entrypoint.sh >/dev/null bash "$repo_root/entrypoint.sh" >/dev/null
grep -q 'page=1' "$curl_log" grep -q 'page=1' "$curl_log"
grep -q 'page=2' "$curl_log" grep -q 'page=2' "$curl_log"
@@ -138,6 +74,11 @@ grep -q '/releases/2' "$curl_log"
grep -q '/releases/1' "$curl_log" grep -q '/releases/1' "$curl_log"
if grep -q '/releases/3' "$curl_log" || grep -q '/releases/4' "$curl_log"; then if grep -q '/releases/3' "$curl_log" || grep -q '/releases/4' "$curl_log"; then
printf 'unexpected deletion request in %s\n' "$curl_log" >&2 printf 'unexpected release deletion request in %s\n' "$curl_log" >&2
exit 1
fi
if grep -q '/tags/' "$curl_log"; then
printf 'unexpected tag deletion request in %s\n' "$curl_log" >&2
exit 1 exit 1
fi fi
+104
View File
@@ -0,0 +1,104 @@
#!/usr/bin/env bash
set -Eeuo pipefail
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(cd "$script_dir/.." && pwd)"
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
curl_log="$tmpdir/curl.log"
bin_dir="$tmpdir/bin"
mkdir -p "$bin_dir"
cat >"$bin_dir/curl" <<'EOF'
#!/usr/bin/env bash
set -Eeuo pipefail
log_file="${CURL_LOG:?}"
printf '%s\n' "$*" >>"$log_file"
last_arg="${!#}"
if [[ " $* " == *" -X DELETE "* ]]; then
printf '204'
exit 0
fi
# 倉庫所有 tagv1~v4 加上兩個未指定 release 的孤兒 tag
if [[ "$last_arg" == */tags?* ]]; then
case "$last_arg" in
*page=1)
printf '%s' '[{"name":"v4"},{"name":"v3"},{"name":"v2"},{"name":"v1"},{"name":"tmp-build"},{"name":"v0"}]'
;;
*)
printf '%s' '[]'
;;
esac
exit 0
fi
# /releases GET:刪除舊版本後重新取得時,只回傳仍保留的 release
if grep -Eq -- '-X DELETE .*/releases/[0-9]' "$log_file"; then
case "$last_arg" in
*page=1)
printf '%s' '[{"id":4,"tag_name":"v4","name":"release-4","created_at":"2024-04-01T00:00:00Z"},{"id":3,"tag_name":"v3","name":"release-3","created_at":"2024-03-01T00:00:00Z"}]'
;;
*)
printf '%s' '[]'
;;
esac
exit 0
fi
case "$last_arg" in
*page=1)
printf '%s' '[{"id":4,"tag_name":"v4","name":"release-4","created_at":"2024-04-01T00:00:00Z"},{"id":3,"tag_name":"v3","name":"release-3","created_at":"2024-03-01T00:00:00Z"}]'
;;
*page=2)
printf '%s' '[{"id":2,"tag_name":"v2","name":"release-2","created_at":"2024-02-01T00:00:00Z"},{"id":1,"tag_name":"v1","name":"release-1","created_at":"2024-01-01T00:00:00Z"}]'
;;
*)
printf '%s' '[]'
;;
esac
EOF
chmod +x "$bin_dir/curl"
cp "$script_dir/lib/jq_mock.py" "$bin_dir/jq.py"
chmod +x "$bin_dir/jq.py"
cat >"$bin_dir/jq" <<'EOF'
#!/usr/bin/env bash
set -Eeuo pipefail
exec python3 "$0.py" "$@"
EOF
chmod +x "$bin_dir/jq"
PATH="$bin_dir:$PATH" \
CURL_LOG="$curl_log" \
GITEA_SERVER_URL="https://gitea.example.test" \
GITEA_REPOSITORY="owner/repo" \
KEEP_COUNT="2" \
RUNNER_TOKEN="" \
bash "$repo_root/entrypoint.sh" >/dev/null
# 舊版本 release 仍被刪除
grep -q '/releases/2' "$curl_log"
grep -q '/releases/1' "$curl_log"
# 未指定 release 的 tag(含被刪除 release 留下的 tag 與其他孤兒 tag)被刪除
for tag in v2 v1 tmp-build v0; do
if ! grep -q "/tags/$tag" "$curl_log"; then
printf 'expected tag deletion /tags/%s not found in %s\n' "$tag" "$curl_log" >&2
exit 1
fi
done
# 仍指定 release 的 tag 不可被刪除
for tag in v4 v3; do
if grep -q "/tags/$tag" "$curl_log"; then
printf 'unexpected deletion of release-assigned tag /tags/%s in %s\n' "$tag" "$curl_log" >&2
exit 1
fi
done
+98
View File
@@ -0,0 +1,98 @@
#!/usr/bin/env python3
"""Minimal jq stand-in for entrypoint.sh tests.
Only the jq expressions used by entrypoint.sh are implemented. Unknown
expressions fail loudly so new jq usage is noticed instead of silently
producing wrong results.
"""
import json
import re
import sys
expr = ""
flags = set()
named_args = {}
args = sys.argv[1:]
while args:
current = args.pop(0)
if current in {"-e", "-c", "-r", "-s"}:
flags.add(current)
continue
if current == "--arg":
key = args.pop(0)
value = args.pop(0)
named_args[key] = value
continue
if current == "--":
if not args:
raise SystemExit("missing jq expression")
expr = args.pop(0)
break
if current.startswith("-"):
flags.add(current)
continue
expr = current
break
raw = sys.stdin.read()
def dump(value):
if "-r" in flags and isinstance(value, (str, int, float)) and not isinstance(value, bool):
sys.stdout.write(str(value))
else:
sys.stdout.write(json.dumps(value, separators=(",", ":")))
if expr == "length":
data = json.loads(raw or "null")
print(len(data))
raise SystemExit(0)
if expr == "add":
arrays = [json.loads(line) for line in raw.splitlines() if line.strip()]
merged = []
for item in arrays:
merged.extend(item)
dump(merged)
raise SystemExit(0)
if expr == "sort_by(.created_at) | reverse":
data = json.loads(raw or "[]")
dump(sorted(data, key=lambda item: item["created_at"], reverse=True))
raise SystemExit(0)
if expr == "[.[].tag_name]":
data = json.loads(raw or "[]")
dump([item["tag_name"] for item in data])
raise SystemExit(0)
match = re.fullmatch(r"any\(\.\[\]; \. == \$(\w+)\)", expr)
if match:
data = json.loads(raw or "[]")
needle = named_args[match.group(1)]
sys.stdout.write("true" if any(item == needle for item in data) else "false")
raise SystemExit(0)
match = re.fullmatch(r"\.\[(\d+):\]", expr)
if match:
data = json.loads(raw or "[]")
dump(data[int(match.group(1)):])
raise SystemExit(0)
if expr == ".[]":
data = json.loads(raw or "[]")
for item in data:
dump(item)
sys.stdout.write("\n")
raise SystemExit(0)
match = re.fullmatch(r"\.(id|tag_name|name)", expr)
if match:
data = json.loads(raw or "null")
value = data.get(match.group(1))
dump(value)
raise SystemExit(0)
raise SystemExit(f"unsupported jq expression: {expr}")