jtube-tv/scripts/e2e-api-check.sh
Jeena 83dde8ba74 test: Add e2e release gate (script + manual checklist)
Releases need more than eyeballs: scripts/e2e-api-check.sh now runs
18 assertions over the live instance (login/refresh, listings, private
rows, subscriptions incl. remote channels, watching + stored position,
file token, playlist reachability) and TESTS.md defines the manual
D-pad checkout that a script cannot do.

Also switch the channels screen to the user's own channels (the
search-based local listing rejects empty queries) and resolve the real
account name at login since logins accept email addresses.
2026-10-10 11:02:22 +00:00

112 lines
No EOL
6.9 KiB
Bash
Executable file

#!/usr/bin/env bash
#
# e2e-api-check.sh — scripted end-to-end smoke test against a live PeerTube instance.
#
# Usage: ./e2e-api-check.sh (uses ~/.agents/secrets/jtube.env via sops)
# sops -d <file>.env | bash <(cat e2e-api-check.sh) — alternative pipe form
#
# Requires: curl, python3. The account may be read-only-ish; only /watching POSTs and
# history reads are needed besides the listings. Prints PASS/FAIL per group; exits
# non-zero if any FAIL.
set -u
pass=0; fail=0
ok() { pass=$((pass+1)); echo "PASS $1"; }
bad() { fail=$((fail+1)); echo "FAIL $1"; }
assert() { # assert <name> <true/false>
if [ "${2:-x}" = "true" ]; then ok "$1"; else bad "$1"; fi
}
# ---- credentials -----------------------------------------------------------
if [ "${JTUBE_URL:-}" = "" ]; then
if command -v sops >/dev/null && [ -f "$HOME/.agents/secrets/jtube.env" ]; then
eval "$(sops -d --output-type dotenv "$HOME/.agents/secrets/jtube.env" | sed 's/^/export /')"
else
echo "No credentials: set JTUBE_URL/JTUBE_USER/JTUBE_PASS or provide the store"; exit 2
fi
fi
# ---- login -----------------------------------------------------------------
CLIENT=$(curl -s "$JTUBE_URL/api/v1/oauth-clients/local")
CID=$(echo "$CLIENT" | python3 -c "import json,sys;print(json.load(sys.stdin)['client_id'])")
CSEC=$(echo "$CLIENT" | python3 -c "import json,sys;print(json.load(sys.stdin)['client_secret'])")
TOKJSON=$(curl -s -X POST "$JTUBE_URL/api/v1/users/token" \
-d "grant_type=password" -d "client_id=$CID" -d "client_secret=$CSEC" \
-d "username=$JTUBE_USER" -d "password=$JTUBE_PASS")
TOK=$(echo "$TOKJSON" | python3 -c "import json,sys;print(json.load(sys.stdin).get('access_token',''))")
RTOK=$(echo "$TOKJSON" | python3 -c "import json,sys;print(json.load(sys.stdin).get('refresh_token',''))")
assert "F0 login (password grant)" $([ -n "$TOK" ] && [ -n "$RTOK" ] && echo true || echo false)
AUTH="Authorization: Bearer $TOK"
jqp() { python3 -c "import json,sys;d=json.load(sys.stdin);import functools;print(eval('d'+sys.argv[1]))" "$1" 2>/dev/null; }
# ---- F0b: refresh flow -----------------------------------------------------
NEW=$(curl -s -X POST "$JTUBE_URL/api/v1/users/token" \
-d "grant_type=refresh_token" -d "client_id=$CID" -d "client_secret=$CSEC" -d "refresh_token=$RTOK" \
| jqp "['access_token']")
assert "F0 token refresh" $([ -n "$NEW" ] && echo true || echo false)
TOK="$NEW"; AUTH="Authorization: Bearer $TOK"
# ---- F1 public local videos ------------------------------------------------
L=$(curl -s "$JTUBE_URL/api/v1/videos?isLocal=true&count=10&sort=-publishedAt")
N=$(echo "$L" | jqp "['total']")
assert "F1 local videos total>0" $([ "${N:-0}" -gt 0 ] && echo true || echo false)
FIRST_UUID=$(echo "$L" | jqp "['data'][0]['shortUUID']")
# ---- F1 me/videos incl. private -------------------------------------------
M=$(curl -s -H "$AUTH" "$JTUBE_URL/api/v1/users/me/videos?count=25&sort=-publishedAt")
assert "F1 my-videos wrapped {total,data}" $(echo "$M" | python3 -c "import json,sys;d=json.load(sys.stdin);print('true' if 'total' in d and isinstance(d.get('data'),list) else 'false')")
assert "F1 my-videos has entries" $(echo "$M" | jqp "['total'] > 0 and 'true' or 'false'")
PRIV=$(echo "$M" | python3 -c "
import json,sys
d=json.load(sys.stdin)
print('true' if any(v['privacy']['id']!=1 for v in d['data']) else 'false')")
assert "F1 non-public entries present (seeded test videos)" "$PRIV"
# ---- F2 own channels -------------------------------------------------------
USERNAME=$(curl -s -H "$AUTH" "$JTUBE_URL/api/v1/users/me" | jqp "['username']")
AC=$(curl -s -H "$AUTH" "$JTUBE_URL/api/v1/accounts/$USERNAME/video-channels")
assert "F2 own channels listed" $(echo "$AC" | python3 -c "import json,sys;print('true' if 'total' in json.load(sys.stdin) else 'false')")
CN=$(echo "$AC" | python3 -c "import json,sys; d=json.load(sys.stdin)['data'][0]; print(d['name']+'@'+d['host'])")
CV=$(curl -s -H "$AUTH" "$JTUBE_URL/api/v1/video-channels/$CN/videos?count=3")
assert "F2 channel videos endpoint" $(echo "$CV" | python3 -c "import json,sys;print('true' if 'total' in json.load(sys.stdin) else 'false')")
LC=$(curl -s "$JTUBE_URL/api/v1/video-channels?search=tube&searchTarget=local&count=5")
assert "F2 local-channel search (non-empty q)" $(echo "$LC" | python3 -c "import json,sys;print('true' if 'total' in json.load(sys.stdin) else 'false')")
# ---- F3 search --------------------------------------------------------------
S=$(curl -s "$JTUBE_URL/api/v1/videos?search=$FIRST_UUID&isLocal=true&count=5")
S2=$(curl -s "$JTUBE_URL/api/v1/videos?search=test&isLocal=true&count=5")
assert "F3 search returns list shape" $(echo "$S2" | python3 -c "import json,sys;d=json.load(sys.stdin);print('true' if 'total' in d else 'false')")
# ---- F4 subscriptions -------------------------------------------------------
SUBS=$(curl -s -H "$AUTH" "$JTUBE_URL/api/v1/users/me/subscriptions/videos?count=10&sort=-publishedAt")
assert "F4 subscription feed wrapped" $(echo "$SUBS" | python3 -c "import json,sys;d=json.load(sys.stdin);print('true' if 'total' in d else 'false')")
SUBS_REMOTE=$(echo "$SUBS" | python3 -c "
import json,sys,os
d=json.load(sys.stdin)
host=os.environ['JTUBE_URL'].replace('https://','').rstrip('/')
print('true' if any(v['channel']['host']!=host for v in d['data']) else 'false')")
assert "F4 remote-channel entries present" "$SUBS_REMOTE"
# ---- F5+F6 history & watching ----------------------------------------------
H=$(curl -s -H "$AUTH" "$JTUBE_URL/api/v1/users/me/history/videos?count=10")
assert "F5 history wrapped" $(echo "$H" | python3 -c "import json,sys;d=json.load(sys.stdin);print('true' if 'total' in d else 'false')")
CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST -H "$AUTH" -H "Content-Type: application/json" \
-d '{"currentTime":3}' "$JTUBE_URL/api/v1/videos/$FIRST_UUID/watching")
assert "F6 watching -> 204" $([ "$CODE" = "204" ] && echo true || echo false)
UH=$(curl -s -H "$AUTH" "$JTUBE_URL/api/v1/videos/$FIRST_UUID?userHistory=true" | jqp "['userHistory']['currentTime']")
assert "F6 watching stored (userHistory)" $( [ -n "$UH" ] && echo true || echo false )
# ---- playback URLs ----------------------------------------------------------
D=$(curl -s -H "$AUTH" "$JTUBE_URL/api/v1/videos/$FIRST_UUID")
PL=$(echo "$D" | jqp "['streamingPlaylists'][0]['playlistUrl']")
assert "F6 detail exposes stream URL" $([ -n "$PL" ] && echo true || echo false)
VFTCODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST -H "$AUTH" "$JTUBE_URL/api/v1/videos/$FIRST_UUID/token")
assert "F6 file token endpoint exists" $([ "$VFTCODE" = "200" ] && echo true || echo false)
VFT=$(curl -s -X POST -H "$AUTH" "$JTUBE_URL/api/v1/videos/$FIRST_UUID/token" | jqp "['files']['token']")
HC=$(curl -s -o /dev/null -w "%{http_code}" "$PL?videoFileToken=$VFT")
assert "F6 playlist reachable" $([ "$HC" = "200" ] && echo true || echo false)
echo "-----------------------------"
echo "PASS=$pass FAIL=$fail"
[ "$fail" = 0 ]