#!/usr/bin/env bash # Contract for scripts/prune-release-assets.sh, driven by a stub curl on PATH: # only assets of releases outside the keep window go, the window is decided by # release date rather than response order, drafts never consume a keep slot, # releases and tags stay, and an API failure never fails the run. set -euo pipefail repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" script="$repo_root/scripts/prune-release-assets.sh" tmp="$(mktemp -d)" trap 'rm -rf "$tmp"' EXIT # Stub curl: one short page of releases, deliberately out of date order and # with a draft that is newer than every published release. Each release embeds # two assets. Every request is appended to $CALLS for the assertions below. mkdir -p "$tmp/bin" cat >"$tmp/bin/curl" <<'STUB' #!/usr/bin/env bash url="${!#}" printf '%s\n' "$*" >>"$CALLS" rel() { printf '{"id":%s,"created_at":"%s","draft":%s,"assets":[{"id":%s01},{"id":%s02}]}' "$1" "$2" "$3" "$1" "$1"; } case "$url" in *releases\?*) [[ "${FAIL_LIST:-0}" == 1 ]] && exit 22 printf '[%s,%s,%s,%s,%s]\n' \ "$(rel 12 2024-01-02T00:00:00Z false)" \ "$(rel 10 2024-01-04T00:00:00Z false)" \ "$(rel 9 2024-06-01T00:00:00Z true)" \ "$(rel 13 2024-01-01T00:00:00Z false)" \ "$(rel 11 2024-01-03T00:00:00Z false)" ;; */assets/*) [[ "${FAIL_DELETE:-0}" == 1 ]] && exit 22 ;; esac exit 0 STUB chmod +x "$tmp/bin/curl" export PATH="$tmp/bin:$PATH" API=https://git.example/api/v1/repos/o/r TOKEN=t run() { CALLS="$tmp/calls" bash "$script" >"$tmp/out" 2>"$tmp/err"; } # Default keep=2: the two newest published releases (10, 11) stay whole, the # older two (12, 13) lose their assets, and the newer draft is ignored. : >"$tmp/calls"; run deletes="$(grep -c -- '-X DELETE' "$tmp/calls" || true)" [[ "$deletes" == 4 ]] || { echo "expected 4 asset deletes, got $deletes" >&2; exit 1; } grep -q 'assets/1201' "$tmp/calls" && grep -q 'assets/1302' "$tmp/calls" || { echo "expected assets of releases 12 and 13 to be deleted" >&2; exit 1; } if grep -q 'releases/10/assets/\|releases/11/assets/' "$tmp/calls"; then echo "kept releases lost assets" >&2; exit 1 fi if grep -q 'releases/9/assets/' "$tmp/calls"; then echo "a draft release was pruned" >&2; exit 1 fi # A release or tag must never be deleted, only attachments under /assets/. if grep -- '-X DELETE' "$tmp/calls" | grep -qv '/assets/'; then echo "a non-asset DELETE was issued" >&2; exit 1 fi # A short page ends the sweep: no second page, so a server ignoring `page` # cannot spin the job until the job timeout. [[ "$(grep -c 'releases?' "$tmp/calls")" == 1 ]] || { echo "a short release page did not end the sweep" >&2; exit 1; } # KEEP=4 covers every published release: nothing to delete. : >"$tmp/calls"; KEEP=4 run if grep -q -- '-X DELETE' "$tmp/calls"; then echo "KEEP=4 still deleted" >&2; exit 1; fi # Failures are warnings, never a non-zero exit. : >"$tmp/calls"; FAIL_DELETE=1 run || { echo "delete failure failed the run" >&2; exit 1; } grep -q 'failed' "$tmp/err" || { echo "delete failure was not warned about" >&2; exit 1; } : >"$tmp/calls"; FAIL_LIST=1 run || { echo "listing failure failed the run" >&2; exit 1; } grep -q 'listing releases failed' "$tmp/err" || { echo "listing failure was not warned about" >&2; exit 1; } if grep -q -- '-X DELETE' "$tmp/calls"; then echo "deleted assets despite an unreadable release list" >&2; exit 1 fi echo "prune-release-assets: prunes by release date outside the keep window, ignores drafts, never deletes a release, never fails the build"