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.
112 lines
No EOL
6.9 KiB
Bash
Executable file
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 ] |