diff --git a/tests/entrypoint_test.sh b/tests/entrypoint_test.sh index 2bf5859..17d5b85 100755 --- a/tests/entrypoint_test.sh +++ b/tests/entrypoint_test.sh @@ -33,7 +33,6 @@ assert_eq() { make_mock_curl() { local bin_dir="$1" - local response_file="$2" cat >"$bin_dir/curl" <<'EOF' #!/bin/sh @@ -45,6 +44,26 @@ if [ "${FAKE_CURL_STATUS:-0}" != "0" ]; then exit "$FAKE_CURL_STATUS" fi +# 分頁模式:依 URL 上的 page=N 回傳對應的 page 檔,超出範圍回傳空陣列 +if [ -n "${FAKE_CURL_PAGES_DIR:-}" ]; then + page=1 + for arg in "$@"; do + case "$arg" in + *page=*) + page=${arg##*page=} + page=${page%%&*} + ;; + esac + done + page_file="$FAKE_CURL_PAGES_DIR/$page.json" + if [ -f "$page_file" ]; then + cat "$page_file" + else + printf '[]' + fi + exit 0 +fi + cat "${FAKE_CURL_RESPONSE_FILE:?}" EOF chmod +x "$bin_dir/curl" @@ -55,42 +74,63 @@ make_mock_jq() { cat >"$bin_dir/jq" <<'EOF' #!/bin/sh -is_beta="" -query="" +python3 -c ' +import json, re, sys -while [ "$#" -gt 0 ]; do - case "$1" in - -r) - shift - ;; - --arg) - if [ "$2" = "is_beta" ]; then - is_beta="$3" - fi - shift 3 - ;; - *) - query="$1" - shift - break - ;; - esac -done +args = sys.argv[1:] +named = {} +use_stdin = True +i = 0 +while i < len(args): + a = args[i] + if a == "-r": + i += 1 + elif a == "-n": + use_stdin = False + i += 1 + elif a in ("--arg", "--argjson"): + value = args[i + 2] + named[args[i + 1]] = json.loads(value) if a == "--argjson" else value + i += 3 + else: + i += 1 -python3 -c 'import json, sys -query = sys.argv[1] -is_beta = sys.argv[2] -payload = sys.stdin.read() -try: - data = json.loads(payload) -except Exception: - sys.exit(4) +payload = sys.stdin.read() if use_stdin else "" + +def parse_or_exit(text): + try: + return json.loads(text) + except Exception: + sys.exit(4) + +# merge 模式:$acc + $page +if "acc" in named and "page" in named: + sys.stdout.write(json.dumps(named["acc"] + named["page"])) + sys.exit(0) + +# count 模式:陣列長度,非陣列回 -1 +if "is_beta" not in named: + data = parse_or_exit(payload) + sys.stdout.write(str(len(data)) if isinstance(data, list) else "-1") + sys.exit(0) + +# calculate 模式 +data = parse_or_exit(payload) +is_beta = named.get("is_beta", "") + +def to_tuple(ver): + nums = [] + for part in ver.split("."): + try: + nums.append(int(part)) + except Exception: + nums.append(0) + while len(nums) < 3: + nums.append(0) + return tuple(nums[:3]) def next_version(latest): - parts = [int(p or 0) for p in latest.split(".")] - while len(parts) < 3: - parts.append(0) - major, minor, patch = parts[:3] + major, minor, patch = to_tuple(latest) patch += 1 if patch >= 10: patch = 0 @@ -98,18 +138,17 @@ def next_version(latest): if minor >= 10: minor = 0 major += 1 - return f"{major}.{minor}.{patch}" + return "{}.{}.{}".format(major, minor, patch) -def beta_max(data, prefix): +def beta_max(items, prefix): values = [] - for item in data: + for item in items: if not isinstance(item, dict): continue tag = item.get("tag_name") if isinstance(tag, str) and tag.startswith(prefix): - suffix = tag[len(prefix):] try: - values.append(int(suffix)) + values.append(int(tag[len(prefix):])) except Exception: pass return max(values) if values else 0 @@ -117,22 +156,24 @@ def beta_max(data, prefix): if not isinstance(data, list): base = "0.0.0" else: - base = "0.0.0" + stable = [] for item in data: if not isinstance(item, dict): continue tag = item.get("tag_name") if isinstance(tag, str) and "-beta." not in tag: - base = tag[1:] if tag.startswith("v") else tag - break + ver = tag[1:] if tag.startswith("v") else tag + if re.match(r"^[0-9]+(\.[0-9]+)*$", ver): + stable.append(ver) + base = max(stable, key=to_tuple) if stable else "0.0.0" next_ver = next_version(base) if is_beta == "true": - beta = beta_max(data, f"v{next_ver}-beta.") + 1 - sys.stdout.write(f"{base}\t{next_ver}-beta.{beta}") + beta = beta_max(data, "v{}-beta.".format(next_ver)) + 1 + sys.stdout.write("{}\t{}-beta.{}".format(base, next_ver, beta)) else: - sys.stdout.write(f"{base}\t{next_ver}") -' "$query" "$is_beta" + sys.stdout.write("{}\t{}".format(base, next_ver)) +' "$@" EOF chmod +x "$bin_dir/jq" } @@ -281,6 +322,69 @@ test_null_release_payload() { assert_eq "version=0.0.1" "$(cat "$output_file")" "null release payload" } +test_pagination_collects_all_pages() { + local workdir + local pages_dir + local bin_dir + local output_file + local stdout_file + local stderr_file + local curl_args + local i + + workdir="$(mktemp -d)" + CLEANUP_PATHS+=("$workdir") + pages_dir="$workdir/pages" + mkdir -p "$pages_dir" + + # 第 1 頁滿載 10 筆,且全部是 beta(重現「最新一整頁都是 beta」的情境) + { + printf '[' + for i in $(seq 1 10); do + if [ "$i" -gt 1 ]; then printf ','; fi + printf '{"tag_name":"v1.9.0-beta.%s"}' "$i" + done + printf ']' + } >"$pages_dir/1.json" + + # 第 2 頁才出現穩定版,且故意讓最大值不是第一筆(驗證 semver 取最大) + cat >"$pages_dir/2.json" <<'JSON' +[ + {"tag_name":"v1.2.3"}, + {"tag_name":"v1.3.1"}, + {"tag_name":"v1.2.9"} +] +JSON + + bin_dir="$workdir/bin" + mkdir -p "$bin_dir" + make_mock_curl "$bin_dir" + make_mock_jq "$bin_dir" + + output_file="$workdir/github_output" + stdout_file="$workdir/stdout" + stderr_file="$workdir/stderr" + + FAKE_CURL_STATUS=0 \ + FAKE_CURL_PAGES_DIR="$pages_dir" \ + FAKE_CURL_LOG_FILE="$workdir/curl_args" \ + PATH="$bin_dir:$PATH" \ + GITEA_SERVER_URL="https://gitea.example.com" \ + GITEA_REPOSITORY="org/repo" \ + IS_BETA="false" \ + GITHUB_OUTPUT="$output_file" \ + bash "$ROOT_DIR/entrypoint.sh" >"$stdout_file" 2>"$stderr_file" + + # 穩定版在第 2 頁、最大為 1.3.1 -> 下一版 1.3.2 + assert_eq "version=1.3.2" "$(cat "$output_file")" "pagination + semver max output" + + # 確認確實翻到第 2 頁 + curl_args="$workdir/curl_args" + if ! grep -q 'page=2' "$curl_args"; then + fail "pagination: expected a request for page=2" + fi +} + test_token_auth_header() { local workdir local response_file @@ -392,6 +496,7 @@ test_beta_release_flow test_empty_release_list test_only_beta_releases test_null_release_payload +test_pagination_collects_all_pages test_token_auth_header test_malformed_release_payload test_curl_failure