11#! /usr/bin/env bash
22#
3- # End-to-end test: does a fresh install actually index media?
3+ # End-to-end test: does a fresh install actually serve media?
44#
5- # Mints a JWT at the mock issuer, triggers a library scan through the real GraphQL API,
6- # and polls until the scanner has produced shows and movies. Everything goes through the
7- # same path a real client uses, so it also covers the chart's OIDC_URL wiring and the
8- # auth layer — not just the scanner.
5+ # Mints a JWT at the mock issuer and runs the scenario scripts in ci/e2e/ in order:
6+ # scanning every library type, podcast subscribe/refresh, HLS streaming with a real
7+ # transcode, epub resources + reading progress, search and watch status. Everything
8+ # goes through the same path a real client uses, so it also covers the chart's
9+ # OIDC_URL wiring and the auth layer — not just the scanner.
910#
1011# Usage: ci/e2e.sh [release] [namespace]
12+ # E2E_ONLY=<pattern> — run only scenario files matching the glob (e.g. E2E_ONLY=30-*)
13+ # E2E_SKIP=<pattern> — skip scenario files matching the glob
1114
1215set -euo pipefail
1316
14- RELEASE=" ${1:- ister} "
15- NAMESPACE=" ${2:- ister} "
16- SERVER_PORT=18080
17- OIDC_PORT=18081
18- TIMEOUT_SECONDS=" ${TIMEOUT_SECONDS:- 240} "
17+ SCRIPT_DIR=$( cd -- " $( dirname -- " ${BASH_SOURCE[0]} " ) " & > /dev/null && pwd )
1918
20- API=" http://localhost:${SERVER_PORT} /api"
21- PIDS=()
19+ export RELEASE=" ${1:- ister} "
20+ export NAMESPACE=" ${2:- ister} "
21+ export SERVER_PORT=18080
22+ export OIDC_PORT=18081
23+ export API=" http://localhost:${SERVER_PORT} /api"
2224
25+ PIDS=()
2326cleanup () {
2427 for pid in " ${PIDS[@]:- } " ; do
2528 kill " $pid " 2> /dev/null || true
2629 done
2730}
2831trap cleanup EXIT
2932
30- fail () { echo " FAIL: $* " >&2 ; exit 1; }
31-
32- forward () { # svc port -> localhost port
33- kubectl port-forward -n " $NAMESPACE " " svc/$1 " " $3 :$2 " > /dev/null 2>&1 &
34- PIDS+=($! )
35- }
36-
37- wait_for () { # url
38- for _ in $( seq 1 30) ; do
39- curl -fsS -o /dev/null " $1 " 2> /dev/null && return 0
40- sleep 1
41- done
42- return 1
43- }
44-
45- # GraphQL over HTTP. Passes the query as a JSON string via jq so quoting can't bite us.
46- gql () { # query [token]
47- local body auth=()
48- body=$( jq -n --arg q " $1 " ' {query: $q}' )
49- [ -n " ${2:- } " ] && auth=(-H " Authorization: Bearer $2 " )
50- curl -fsS -X POST " $API /graphql" \
51- -H ' Content-Type: application/json' \
52- " ${auth[@]} " \
53- -d " $body "
54- }
33+ source " $SCRIPT_DIR /e2e/lib.sh"
5534
5635echo " ==> Port-forwarding"
5736forward " ${RELEASE} -server" 8080 " $SERVER_PORT "
@@ -62,45 +41,31 @@ wait_for "http://localhost:${OIDC_PORT}/default/.well-known/openid-configuration
6241 || fail " mock issuer not reachable on :${OIDC_PORT} "
6342
6443echo " ==> Minting a token with roles=[user]"
65- # The Host header matters: mock-oauth2-server builds the `iss` claim from it, and the
66- # server rejects any token whose `iss` differs from OIDC_URL (http://mock-oidc:8080/default).
67- TOKEN=$( curl -fsS -X POST " http://localhost:${OIDC_PORT} /default/token" \
68- -H ' Host: mock-oidc:8080' \
69- -d grant_type=client_credentials \
70- -d client_id=ci \
71- -d client_secret=ci-secret \
72- -d scope=ister | jq -r ' .access_token' )
73- [ -n " $TOKEN " ] && [ " $TOKEN " != " null" ] || fail " no access_token from the mock issuer"
44+ mint_token
45+ export TOKEN
7446
7547echo " ==> Checking the token is actually accepted (authenticated query)"
7648# If OIDC_URL, the issuer or the roles claim were wrong, this is where it shows up —
7749# with a clear error, rather than as a mysteriously empty library later on.
78- libraries=$( gql ' { libraries { id name type } }' " $TOKEN " )
50+ libraries=$( gql ' { libraries { id name type } }' )
7951echo " $libraries " | jq -e ' .errors' > /dev/null 2>&1 \
8052 && fail " authenticated query rejected: $( echo " $libraries " | jq -c ' .errors' ) "
8153echo " $libraries " | jq -e ' .data.libraries | length >= 2' > /dev/null \
82- || fail " expected the 2 configured libraries, got: $( echo " $libraries " | jq -c ' .data.libraries' ) "
54+ || fail " expected the configured libraries, got: $( echo " $libraries " | jq -c ' .data.libraries' ) "
8355
84- echo " ==> Triggering the library scan"
85- scan=$( gql ' mutation { scanLibrary }' " $TOKEN " )
86- echo " $scan " | jq -e ' .data.scanLibrary == true' > /dev/null \
87- || fail " scanLibrary did not return true: $scan "
88-
89- # The scan is asynchronous (RabbitMQ events, then ffprobe per file). Rows appear within
90- # seconds, but poll rather than sleep — a fixed sleep is either flaky or slow.
91- echo " ==> Waiting for the scanner to index media (up to ${TIMEOUT_SECONDS} s)"
92- deadline=$(( SECONDS + TIMEOUT_SECONDS))
93- shows=0
94- movies=0
95- while [ $SECONDS -lt $deadline ]; do
96- shows=$( gql ' { shows(size: 1) { totalElements } }' " $TOKEN " | jq -r ' .data.shows.totalElements // 0' )
97- movies=$( gql ' { movies(size: 1) { totalElements } }' " $TOKEN " | jq -r ' .data.movies.totalElements // 0' )
98- echo " shows=$shows movies=$movies "
99- if [ " $shows " -gt 0 ] && [ " $movies " -gt 0 ]; then
100- echo " ==> PASS: indexed $shows show(s) and $movies movie(s)"
101- exit 0
56+ for scenario in " $SCRIPT_DIR " /e2e/[0-9]* .sh; do
57+ name=$( basename " $scenario " )
58+ if [ -n " ${E2E_ONLY:- } " ] && [[ " $name " != ${E2E_ONLY} ]]; then
59+ echo " ==> Skipping $name (E2E_ONLY=${E2E_ONLY} )"
60+ continue
61+ fi
62+ if [ -n " ${E2E_SKIP:- } " ] && [[ " $name " == ${E2E_SKIP} ]]; then
63+ echo " ==> Skipping $name (E2E_SKIP=${E2E_SKIP} )"
64+ continue
10265 fi
103- sleep 5
66+ echo " ==> Scenario $name "
67+ # Scenarios run in this shell so they share TOKEN, API and the helpers.
68+ source " $scenario "
10469done
10570
106- fail " nothing indexed within ${TIMEOUT_SECONDS} s (shows= $shows movies= $movies ) "
71+ echo " ==> PASS: all scenarios completed "
0 commit comments