test: expose non-atomic Forgejo releases
Some checks failed
CI / action-exercise (pull_request) Successful in 7s
CI / self-guards (pull_request) Successful in 9s
CI / release-exercise (pull_request) Successful in 12s
CI / docs-sync-exercise (pull_request) Successful in 6s
Refs guard / refs-not-closing (pull_request) Successful in 6s
labels / labels (pull_request) Successful in 8s
CI / test (pull_request) Failing after 3m57s
Some checks failed
CI / action-exercise (pull_request) Successful in 7s
CI / self-guards (pull_request) Successful in 9s
CI / release-exercise (pull_request) Successful in 12s
CI / docs-sync-exercise (pull_request) Successful in 6s
Refs guard / refs-not-closing (pull_request) Successful in 6s
labels / labels (pull_request) Successful in 8s
CI / test (pull_request) Failing after 3m57s
This commit is contained in:
parent
f5c02fee8f
commit
2ab9902c38
1 changed files with 126 additions and 16 deletions
|
|
@ -952,16 +952,25 @@ check "...and never hits the flat /pulls/{n}/comments endpoint" 1 "" \
|
|||
# tag creation GitHub POST /git/refs
|
||||
# Forgejo POST /tags (/git/refs is GET-only)
|
||||
|
||||
# release_stub <code> <body> — a curl stub answering one canned response and
|
||||
# recording the method+path it was asked for.
|
||||
# release_stub <code> <body> [<code> <body>…] — a curl stub answering a
|
||||
# sequence of canned responses and recording the method+path it was asked for.
|
||||
# The cursor lives in a file because forge_release_create captures
|
||||
# forgejo_write in a command substitution; an in-memory counter would advance
|
||||
# only in that subshell and every later request would replay response one.
|
||||
release_stub() {
|
||||
# Globals, not locals: the curl closure below runs long after this
|
||||
# function returns, exactly as stub_writes does above.
|
||||
STUB_CODE="$1" STUB_BODY="$2"
|
||||
STUB_CODES=() STUB_BODIES=()
|
||||
while [ "$#" -gt 0 ]; do
|
||||
STUB_CODES+=("$1") STUB_BODIES+=("$2")
|
||||
shift 2
|
||||
done
|
||||
STUB_READ_CODE="${STUB_CODES[0]}" STUB_READ_BODY="${STUB_BODIES[0]}"
|
||||
printf '0\n' >"$TMP/release-stub-cursor"
|
||||
: >"$WRITES"
|
||||
# shellcheck disable=SC2317 # invoked indirectly, by the forge verbs
|
||||
curl() {
|
||||
local hdr="" out="" method=GET url="" payload=""
|
||||
local hdr="" out="" method=GET url="" payload="" fail_on_http=false
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-D) hdr="$2"; shift ;;
|
||||
|
|
@ -969,18 +978,32 @@ release_stub() {
|
|||
-X) method="$2"; shift ;;
|
||||
-d) payload="$2"; shift ;;
|
||||
-F) payload="$payload -F $2"; shift ;;
|
||||
-f) fail_on_http=true ;;
|
||||
-H) shift ;;
|
||||
-*) ;;
|
||||
*) url="$1" ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
[ -n "$hdr" ] && printf 'HTTP/1.1 %s x\r\n\r\n' "$STUB_CODE" >"$hdr"
|
||||
[ -n "$out" ] && printf '%s' "$STUB_BODY" >"$out"
|
||||
local index code body
|
||||
if [ "$method" = GET ]; then
|
||||
code="$STUB_READ_CODE" body="$STUB_READ_BODY"
|
||||
else
|
||||
index="$(cat "$TMP/release-stub-cursor")"
|
||||
code="${STUB_CODES[$index]:-${STUB_CODES[${#STUB_CODES[@]} - 1]}}"
|
||||
body="${STUB_BODIES[$index]:-${STUB_BODIES[${#STUB_BODIES[@]} - 1]}}"
|
||||
printf '%s\n' "$((index + 1))" >"$TMP/release-stub-cursor"
|
||||
fi
|
||||
[ -n "$hdr" ] && printf 'HTTP/1.1 %s x\r\n\r\n' "$code" >"$hdr"
|
||||
[ -n "$out" ] && printf '%s' "$body" >"$out"
|
||||
printf '%s %s %s\n' "$method" "${url##*/api/v1/}" "$payload" >>"$WRITES"
|
||||
if [ "$fail_on_http" = true ]; then
|
||||
case "$code" in 2*) ;; *) return 22 ;; esac
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
}
|
||||
release_read() { STUB_READ_CODE="$1" STUB_READ_BODY="$2"; }
|
||||
|
||||
GITHUB_API_URL=https://forge.example/api/v1
|
||||
export GITHUB_API_URL
|
||||
|
|
@ -1023,6 +1046,10 @@ check "forgejo: a 500 refuses rather than saying 'none'" 1 "the answer is unknow
|
|||
|
||||
release_stub 200 '{"tag_name":"1.2.3"}'
|
||||
check "forgejo: a present release is yes" 0 "yes" forge_release_exists 1.2.3
|
||||
release_stub 200 '{"tag_name":"1.2.3","draft":true}'
|
||||
check "forgejo: a draft is not a present release" 0 "no" forge_release_exists 1.2.3
|
||||
release_stub 200 '{"tag_name":"1.2.3","draft":false}'
|
||||
check "forgejo: a published release is present" 0 "yes" forge_release_exists 1.2.3
|
||||
release_stub 404 '{}'
|
||||
check "forgejo: an absent release is no" 0 "no" forge_release_exists 1.2.3
|
||||
release_stub 503 '{}'
|
||||
|
|
@ -1046,9 +1073,22 @@ check "forgejo: the tag body names the target sha" 0 '"target":"cafebabe"' \
|
|||
writes_after forge_tag_create 1.2.3 cafebabe
|
||||
|
||||
printf 'notes body\n' >"$TMP/notes.md"
|
||||
release_stub 201 '{"id":42}'
|
||||
release_writes_after() { "$@" >/dev/null 2>&1; grep -v '^GET ' "$WRITES"; }
|
||||
|
||||
release_stub 201 '{"id":42}' 200 '{}'
|
||||
release_read 404 '{}'
|
||||
check "forgejo: the publish POSTs to /releases with the notes as body" 0 '"body":"notes body' \
|
||||
writes_after forge_release_create 1.2.3 1.2.3 "$TMP/notes.md"
|
||||
release_writes_after forge_release_create 1.2.3 1.2.3 "$TMP/notes.md"
|
||||
check "the create stages a draft" 0 '"draft":true' cat "$WRITES"
|
||||
zero_asset_writes() {
|
||||
release_stub 201 '{"id":42}' 200 '{}'
|
||||
release_read 404 '{}'
|
||||
forge_release_create 1.2.3 1.2.3 "$TMP/notes.md" >/dev/null 2>&1
|
||||
local got
|
||||
got="$(grep -v '^GET ' "$WRITES" | sed -E 's/ (\{.*|$)//')"
|
||||
[ "$got" = $'POST repos/o/r/releases\nPATCH repos/o/r/releases/42' ] || { printf '%s\n' "$got"; return 1; }
|
||||
}
|
||||
check "the zero-asset path creates then publishes" 0 "" zero_asset_writes
|
||||
|
||||
# Assets: the hook contract permits any filename the consumer drops in
|
||||
# RELEASE_ASSETS_DIR, and the asset name travels as a QUERY VALUE. Raw
|
||||
|
|
@ -1062,18 +1102,88 @@ check "the encoder escapes the query delimiters" 0 "a%26b%23c%2Bd%25e.tgz" \
|
|||
|
||||
printf 'x\n' >"$TMP/release asset.tgz"
|
||||
printf 'y\n' >"$TMP/a&b.tgz"
|
||||
release_stub 201 '{"id":42}'
|
||||
release_stub 201 '{"id":42}' 201 '{}' 200 '{}'
|
||||
release_read 404 '{}'
|
||||
check "an asset with a space uploads under the encoded name" 0 "assets?name=release%20asset.tgz" \
|
||||
writes_after forge_release_create 1.2.3 1.2.3 "$TMP/notes.md" "$TMP/release asset.tgz"
|
||||
release_stub 201 '{"id":42}'
|
||||
release_writes_after forge_release_create 1.2.3 1.2.3 "$TMP/notes.md" "$TMP/release asset.tgz"
|
||||
release_stub 201 '{"id":42}' 201 '{}' 200 '{}'
|
||||
release_read 404 '{}'
|
||||
check "an asset with '&' does not become two parameters" 0 "assets?name=a%26b.tgz" \
|
||||
writes_after forge_release_create 1.2.3 1.2.3 "$TMP/notes.md" "$TMP/a&b.tgz"
|
||||
release_stub 201 '{"id":42}'
|
||||
release_writes_after forge_release_create 1.2.3 1.2.3 "$TMP/notes.md" "$TMP/a&b.tgz"
|
||||
release_stub 201 '{"id":42}' 201 '{}' 200 '{}'
|
||||
release_read 404 '{}'
|
||||
check "the upload targets the created release id" 0 "releases/42/assets" \
|
||||
writes_after forge_release_create 1.2.3 1.2.3 "$TMP/notes.md" "$TMP/a&b.tgz"
|
||||
release_stub 201 '{"id":42}'
|
||||
release_writes_after forge_release_create 1.2.3 1.2.3 "$TMP/notes.md" "$TMP/a&b.tgz"
|
||||
release_stub 201 '{"id":42}' 201 '{}' 200 '{}'
|
||||
release_read 404 '{}'
|
||||
check "the asset rides as a multipart attachment" 0 "attachment=@" \
|
||||
writes_after forge_release_create 1.2.3 1.2.3 "$TMP/notes.md" "$TMP/a&b.tgz"
|
||||
release_writes_after forge_release_create 1.2.3 1.2.3 "$TMP/notes.md" "$TMP/a&b.tgz"
|
||||
|
||||
happy_asset_writes() {
|
||||
release_stub 201 '{"id":42}' 201 '{}' 201 '{}' 200 '{}'
|
||||
release_read 404 '{}'
|
||||
forge_release_create 1.2.3 1.2.3 "$TMP/notes.md" "$TMP/release asset.tgz" "$TMP/a&b.tgz" >/dev/null 2>&1
|
||||
local got want
|
||||
got="$(grep -v '^GET ' "$WRITES" | sed -E 's/ (\{.*| -F.*|$)//')"
|
||||
want=$'POST repos/o/r/releases\nPOST repos/o/r/releases/42/assets?name=release%20asset.tgz\nPOST repos/o/r/releases/42/assets?name=a%26b.tgz\nPATCH repos/o/r/releases/42'
|
||||
[ "$got" = "$want" ] || { printf '%s\n' "$got"; return 1; }
|
||||
}
|
||||
check "assets upload in argument order before the release is published" 0 "" happy_asset_writes
|
||||
|
||||
upload_failure() {
|
||||
release_stub 201 '{"id":42}' 500 '{}' 204 '{}'
|
||||
release_read 404 '{}'
|
||||
forge_release_create 1.2.3 1.2.3 "$TMP/notes.md" "$TMP/a&b.tgz"
|
||||
}
|
||||
check "an upload failure returns non-zero" 1 "asset upload failed" upload_failure
|
||||
check "an upload failure deletes the draft" 0 "DELETE repos/o/r/releases/42" cat "$WRITES"
|
||||
|
||||
flip_failure() {
|
||||
release_stub 201 '{"id":42}' 500 '{}' 204 '{}'
|
||||
release_read 404 '{}'
|
||||
forge_release_create 1.2.3 1.2.3 "$TMP/notes.md"
|
||||
}
|
||||
check "a publish flip failure returns non-zero" 1 "HTTP 500" flip_failure
|
||||
check "a publish flip failure deletes the draft" 0 "DELETE repos/o/r/releases/42" cat "$WRITES"
|
||||
|
||||
create_failure() {
|
||||
release_stub 500 '{}'
|
||||
release_read 404 '{}'
|
||||
forge_release_create 1.2.3 1.2.3 "$TMP/notes.md"
|
||||
}
|
||||
check "a create failure returns non-zero" 1 "HTTP 500" create_failure
|
||||
check "a create failure has nothing to delete" 1 "" grep -q '^DELETE ' "$WRITES"
|
||||
|
||||
missing_release_id() {
|
||||
release_stub 201 '{}'
|
||||
release_read 404 '{}'
|
||||
forge_release_create 1.2.3 1.2.3 "$TMP/notes.md"
|
||||
}
|
||||
check "a create without an id keeps its refusal" 1 "returned no release id" missing_release_id
|
||||
check "a create without an id deletes nothing unknown" 1 "" grep -q '^DELETE ' "$WRITES"
|
||||
|
||||
stranded_cleanup_failure() {
|
||||
release_stub 201 '{"id":42}' 500 '{"message":"upload refused"}' 500 '{"message":"delete refused"}'
|
||||
release_read 404 '{}'
|
||||
forge_release_create 1.2.3 1.2.3 "$TMP/notes.md" "$TMP/a&b.tgz"
|
||||
}
|
||||
check "cleanup failure preserves the original failure" 1 "asset upload failed" stranded_cleanup_failure
|
||||
check "cleanup failure also names the stranded draft" 1 "stranded draft 42 for tag '1.2.3'" stranded_cleanup_failure
|
||||
|
||||
release_stub 204 '{}' 201 '{"id":42}' 200 '{}'
|
||||
release_read 200 '{"id":41,"tag_name":"1.2.3","draft":true}'
|
||||
forge_release_create 1.2.3 1.2.3 "$TMP/notes.md" >/dev/null 2>&1
|
||||
stranded_draft_order() {
|
||||
local got
|
||||
got="$(grep -v '^GET ' "$WRITES" | head -2 | sed -E 's/ (\{.*|$)//')"
|
||||
[ "$got" = $'DELETE repos/o/r/releases/41\nPOST repos/o/r/releases' ] || { printf '%s\n' "$got"; return 1; }
|
||||
}
|
||||
check "a stranded same-tag draft is deleted before create" 0 "" stranded_draft_order
|
||||
|
||||
release_stub 409 '{}'
|
||||
release_read 200 '{"id":41,"tag_name":"1.2.3","draft":false}'
|
||||
forge_release_create 1.2.3 1.2.3 "$TMP/notes.md" >/dev/null 2>&1
|
||||
check "a published same-tag release is never deleted" 1 "" grep -q '^DELETE ' "$WRITES"
|
||||
|
||||
# --- the github twins address their own paths ----------------------------
|
||||
. "$ROOT/lib/forge-github.sh"
|
||||
|
|
|
|||
Loading…
Reference in a new issue