forked from heavy-duty/ceremony
test: expose truncated Forgejo timelines
This commit is contained in:
parent
ca7ce6e919
commit
8c0f5d53d7
1 changed files with 75 additions and 2 deletions
|
|
@ -746,6 +746,7 @@ check "no api base refuses" 1 "cannot reach the forge" \
|
||||||
# .user.login -> .actor.login. Mutation-verified: collapsing add/remove or
|
# .user.login -> .actor.login. Mutation-verified: collapsing add/remove or
|
||||||
# emitting .user instead of .actor each reds its own case (#4853).
|
# emitting .user instead of .actor each reds its own case (#4853).
|
||||||
timeline_stub() {
|
timeline_stub() {
|
||||||
|
: >"$timeline_calls"
|
||||||
# shellcheck disable=SC2317 # invoked indirectly, by forge_api
|
# shellcheck disable=SC2317 # invoked indirectly, by forge_api
|
||||||
curl() {
|
curl() {
|
||||||
local hdr="" out="" url=""
|
local hdr="" out="" url=""
|
||||||
|
|
@ -753,14 +754,44 @@ timeline_stub() {
|
||||||
case "$1" in -D) hdr="$2"; shift ;; -o) out="$2"; shift ;; -H) shift ;; *) url="$1" ;; esac
|
case "$1" in -D) hdr="$2"; shift ;; -o) out="$2"; shift ;; -H) shift ;; *) url="$1" ;; esac
|
||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
printf 'HTTP/1.1 200 OK\r\nX-Total-Count: %s\r\n\r\n' "${FAKE_TL_N:-2}" >"$hdr"
|
printf '%s\n' "$url" >>"$timeline_calls"
|
||||||
|
local page=1 page_body="$FAKE_TIMELINE" page_total="${FAKE_TL_N:-2}"
|
||||||
|
case "$url" in *page=*) page="${url##*page=}"; page="${page%%&*}" ;; esac
|
||||||
|
if [ "${#FAKE_TL_PAGES[@]}" -gt 0 ]; then
|
||||||
|
if [ "$page" -le "${#FAKE_TL_PAGES[@]}" ]; then
|
||||||
|
page_body="${FAKE_TL_PAGES[$((page - 1))]}"
|
||||||
|
else
|
||||||
|
page_body='[]'
|
||||||
|
fi
|
||||||
|
page_total="$(jq 'length' <<<"$page_body")"
|
||||||
|
fi
|
||||||
|
{
|
||||||
|
printf 'HTTP/1.1 200 OK\r\n'
|
||||||
|
[ "${FAKE_TL_HEADERS:-yes}" = no ] || printf 'X-Total-Count: %s\r\n' "$page_total"
|
||||||
|
printf '\r\n'
|
||||||
|
} >"$hdr"
|
||||||
case "$url" in
|
case "$url" in
|
||||||
*timeline*) printf '%s' "$FAKE_TIMELINE" >"$out" ;;
|
*timeline*) printf '%s' "$page_body" >"$out" ;;
|
||||||
*) printf '[]' >"$out" ;;
|
*) printf '[]' >"$out" ;;
|
||||||
esac
|
esac
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
timeline_page() {
|
||||||
|
jq -nc --argjson first "$1" --argjson count "$2" '
|
||||||
|
[range($first; $first + $count)
|
||||||
|
| {
|
||||||
|
type: "label",
|
||||||
|
body: "1",
|
||||||
|
user: {login: "setter"},
|
||||||
|
label: {name: "needs-ruling"},
|
||||||
|
created_at: ("event-" + tostring)
|
||||||
|
}]
|
||||||
|
'
|
||||||
|
}
|
||||||
|
timeline_calls="$TMP/timeline_calls"
|
||||||
|
FAKE_TL_PAGES=()
|
||||||
|
FAKE_TL_HEADERS=yes
|
||||||
FAKE_TIMELINE='[
|
FAKE_TIMELINE='[
|
||||||
{"type":"label","body":"1","user":{"login":"setter"},"label":{"name":"needs-ruling"},"created_at":"2026-08-02T14:58:13Z"},
|
{"type":"label","body":"1","user":{"login":"setter"},"label":{"name":"needs-ruling"},"created_at":"2026-08-02T14:58:13Z"},
|
||||||
{"type":"label","body":"","user":{"login":"setter"},"label":{"name":"needs-ruling"},"created_at":"2026-08-02T15:22:22Z"},
|
{"type":"label","body":"","user":{"login":"setter"},"label":{"name":"needs-ruling"},"created_at":"2026-08-02T15:22:22Z"},
|
||||||
|
|
@ -777,6 +808,48 @@ check "forge_timeline drops non-label events" 0 "" \
|
||||||
test "$(jq '[.[] | select(.event == null or .event == "")] | length' <<<"$tl")" = 0
|
test "$(jq '[.[] | select(.event == null or .event == "")] | length' <<<"$tl")" = 0
|
||||||
check "forge_timeline uses .actor.login, not a bare .user" 0 "" \
|
check "forge_timeline uses .actor.login, not a bare .user" 0 "" \
|
||||||
jq -e 'all(.[]; has("actor") and (.user|not))' <<<"$tl" >/dev/null
|
jq -e 'all(.[]; has("actor") and (.user|not))' <<<"$tl" >/dev/null
|
||||||
|
|
||||||
|
# Forgejo's timeline endpoint lies consistently: x-total-count echoes the
|
||||||
|
# current page size. With 151 events its pages declare 50, 50, 50 and 1, so
|
||||||
|
# strict pagination stops successfully after page 1 and drops the newest 101
|
||||||
|
# events. Exhaustion is safe only here because timelines are append-only.
|
||||||
|
FAKE_TL_PAGES=(
|
||||||
|
"$(timeline_page 1 50)"
|
||||||
|
"$(timeline_page 51 50)"
|
||||||
|
"$(timeline_page 101 50)"
|
||||||
|
"$(timeline_page 151 1)"
|
||||||
|
)
|
||||||
|
timeline_stub
|
||||||
|
tl="$(REPO=o/r forge_timeline 188)"
|
||||||
|
check "forge_timeline exhausts all pages despite per-page total headers" 0 "" \
|
||||||
|
test "$(jq 'length' <<<"$tl")" = 151
|
||||||
|
check "forge_timeline retains the newest event beyond page one" 0 "" \
|
||||||
|
jq -e 'any(.[]; .created_at == "event-151")' <<<"$tl" >/dev/null
|
||||||
|
|
||||||
|
# A collection exactly divisible by the page size needs one final empty read;
|
||||||
|
# stopping after the second full page cannot prove exhaustion.
|
||||||
|
FAKE_TL_PAGES=("$(timeline_page 1 50)" "$(timeline_page 51 50)")
|
||||||
|
timeline_stub
|
||||||
|
exhaustive_count="$(REPO=o/r forge_api --paginate-exhaustive 'repos/o/r/issues/188/timeline' --jq 'length')"
|
||||||
|
check "exhaustive pagination terminates after an empty page" 0 "" \
|
||||||
|
test "$exhaustive_count" = 100
|
||||||
|
check "an exactly-full exhaustive gather reads the empty third page" 0 "" \
|
||||||
|
test "$(wc -l <"$timeline_calls")" = 3
|
||||||
|
|
||||||
|
# The exhaustive path's completeness proof is the short page itself; it must
|
||||||
|
# never consult the endpoint's missing or dishonest total header.
|
||||||
|
FAKE_TL_PAGES=("$(timeline_page 1 50)" "$(timeline_page 51 1)")
|
||||||
|
FAKE_TL_HEADERS=no
|
||||||
|
timeline_stub
|
||||||
|
check "exhaustive pagination needs no x-total-count header" 0 "" \
|
||||||
|
eq 51 forge_api --paginate-exhaustive 'repos/o/r/issues/188/timeline' --jq 'length'
|
||||||
|
FAKE_TL_HEADERS=yes
|
||||||
|
|
||||||
|
check "strict and exhaustive pagination are mutually exclusive" 1 "mutually exclusive" \
|
||||||
|
forge_api --paginate --paginate-exhaustive 'repos/o/r/issues/188/timeline'
|
||||||
|
check "exhaustive pagination refuses a non-GET method" 1 "GET" \
|
||||||
|
forge_api --paginate-exhaustive --method POST 'repos/o/r/issues/188/timeline'
|
||||||
|
|
||||||
# Unreadable: curl fails. Status must surface through forge_timeline itself
|
# Unreadable: curl fails. Status must surface through forge_timeline itself
|
||||||
# (not a later jq), or the ruling ladder invents a verdict on a half-read.
|
# (not a later jq), or the ruling ladder invents a verdict on a half-read.
|
||||||
# shellcheck disable=SC2317
|
# shellcheck disable=SC2317
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue