skillhub/scripts/tests/web-base-path-routing-test.sh
philsun 34f244e7a4 feat(web): support configurable base-path deployment
Signed-off-by: philsun <xinyi.sun@daocloud.io>
2026-08-05 12:50:26 +08:00

135 lines
5.4 KiB
Bash
Executable file

#!/bin/sh
set -eu
ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)
ENTRYPOINT="$ROOT_DIR/web/docker-entrypoint.d/20-base-path.sh"
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
web_root="$tmp/html"
routing_config="$tmp/skillhub-base-path.conf"
mkdir -p "$web_root"
printf '%s\n' \
'<link rel="icon" href="/__SKILLHUB_WEB_BASE_PATH__/favicon.svg" />' \
'/__SKILLHUB_WEB_BASE_PATH__/assets/index.js' >"$web_root/index.html"
SKILLHUB_WEB_BASE_PATH=/skillhub/ \
SKILLHUB_WEB_ROOT="$web_root" \
SKILLHUB_NGINX_BASE_PATH_CONFIG="$routing_config" \
sh "$ENTRYPOINT"
grep -F 'location = /skillhub {' "$routing_config" >/dev/null
grep -F 'return 301 /skillhub/;' "$routing_config" >/dev/null
grep -F 'location ^~ /skillhub/ {' "$routing_config" >/dev/null
grep -F 'rewrite ^/skillhub/(.*)$ /$1 last;' "$routing_config" >/dev/null
grep -F 'set $skillhub_forwarded_prefix /skillhub;' "$routing_config" >/dev/null
grep -F '/skillhub/assets/index.js' "$web_root/index.html" >/dev/null
# favicon (and any other absolute asset ref in index.html) picks up the sub-path prefix.
grep -F '/skillhub/favicon.svg' "$web_root/index.html" >/dev/null
# A same-origin API base that does not match the base path must be rejected at startup.
mismatch_root="$tmp/mismatch-html"
mismatch_config="$tmp/mismatch.conf"
mkdir -p "$mismatch_root"
if SKILLHUB_WEB_BASE_PATH=/skillhub/ \
SKILLHUB_WEB_API_BASE_URL=/other \
SKILLHUB_WEB_ROOT="$mismatch_root" \
SKILLHUB_NGINX_BASE_PATH_CONFIG="$mismatch_config" \
sh "$ENTRYPOINT" 2>/dev/null; then
echo 'entrypoint must reject an API base that does not match the base path' >&2
exit 1
fi
if [ "$(grep -Fc 'proxy_set_header X-Forwarded-Prefix $skillhub_forwarded_prefix;' "$ROOT_DIR/web/nginx.conf.template")" -ne 4 ]; then
echo 'API, OAuth, and .well-known proxy locations must forward the configured base path' >&2
exit 1
fi
root_web_root="$tmp/root-html"
root_routing_config="$tmp/root-base-path.conf"
mkdir -p "$root_web_root"
printf '%s\n' '/__SKILLHUB_WEB_BASE_PATH__/assets/index.js' >"$root_web_root/index.html"
SKILLHUB_WEB_BASE_PATH=/ \
SKILLHUB_WEB_ROOT="$root_web_root" \
SKILLHUB_NGINX_BASE_PATH_CONFIG="$root_routing_config" \
sh "$ENTRYPOINT"
grep -F '# No sub-path routing is required for root deployment.' "$root_routing_config" >/dev/null
grep -F 'set $skillhub_forwarded_prefix "";' "$root_routing_config" >/dev/null
if grep -F 'location ' "$root_routing_config" >/dev/null; then
echo 'root deployment must not generate a duplicate Nginx location' >&2
exit 1
fi
grep -F '/assets/index.js' "$root_web_root/index.html" >/dev/null
# Runtime input validation must reject '.'/'..' path segments (they normalize and
# would desync the generated location from the baked asset URLs).
for bad in '/foo/../bar/' '/foo/./bar/' '/foo//bar/' '/no-trailing' 'foo/' \
'/api/' '/oauth2/' '/login/' '/assets/' '/registry/' '/nginx-health/' \
'/.well-known/' '/runtime-config.js/' '/api/nested/'; do
reject_root="$tmp/reject-html"
reject_config="$tmp/reject.conf"
mkdir -p "$reject_root"
if SKILLHUB_WEB_BASE_PATH="$bad" \
SKILLHUB_WEB_ROOT="$reject_root" \
SKILLHUB_NGINX_BASE_PATH_CONFIG="$reject_config" \
sh "$ENTRYPOINT" 2>/dev/null; then
echo "entrypoint must reject invalid SKILLHUB_WEB_BASE_PATH: $bad" >&2
exit 1
fi
rm -rf "$reject_root" "$reject_config"
done
# A fixed-base build (no runtime SKILLHUB_WEB_BASE_PATH) must default to the baked
# base recorded at build time and generate the matching Nginx routing.
baked_root="$tmp/baked-html"
baked_config="$tmp/baked.conf"
baked_file="$tmp/baked-base-path"
mkdir -p "$baked_root"
printf '%s\n' '/skillhub/assets/index.js' >"$baked_root/index.html"
printf '%s' '/skillhub/' >"$baked_file"
SKILLHUB_WEB_ROOT="$baked_root" \
SKILLHUB_NGINX_BASE_PATH_CONFIG="$baked_config" \
SKILLHUB_WEB_BAKED_BASE_PATH_FILE="$baked_file" \
sh "$ENTRYPOINT"
grep -F 'location ^~ /skillhub/ {' "$baked_config" >/dev/null
grep -F 'set $skillhub_forwarded_prefix /skillhub;' "$baked_config" >/dev/null
# The bundled Compose/K8s deploy configs pass SKILLHUB_WEB_BASE_PATH as an empty
# string (unset by the operator). An empty value must be treated as "use the
# baked base", not as root — otherwise a fixed-base image serves broken assets.
empty_config="$tmp/empty.conf"
SKILLHUB_WEB_BASE_PATH= \
SKILLHUB_WEB_ROOT="$baked_root" \
SKILLHUB_NGINX_BASE_PATH_CONFIG="$empty_config" \
SKILLHUB_WEB_BAKED_BASE_PATH_FILE="$baked_file" \
sh "$ENTRYPOINT"
grep -F 'location ^~ /skillhub/ {' "$empty_config" >/dev/null
grep -F 'set $skillhub_forwarded_prefix /skillhub;' "$empty_config" >/dev/null
# A runtime base that conflicts with the baked base must fail loudly rather than
# silently serving mismatched routing/assets.
conflict_config="$tmp/conflict.conf"
if SKILLHUB_WEB_BASE_PATH=/other/ \
SKILLHUB_WEB_ROOT="$baked_root" \
SKILLHUB_NGINX_BASE_PATH_CONFIG="$conflict_config" \
SKILLHUB_WEB_BAKED_BASE_PATH_FILE="$baked_file" \
sh "$ENTRYPOINT" 2>/dev/null; then
echo 'entrypoint must reject a runtime base that conflicts with the baked base' >&2
exit 1
fi
# An explicit runtime base matching the baked base is accepted.
match_config="$tmp/match.conf"
SKILLHUB_WEB_BASE_PATH=/skillhub/ \
SKILLHUB_WEB_ROOT="$baked_root" \
SKILLHUB_NGINX_BASE_PATH_CONFIG="$match_config" \
SKILLHUB_WEB_BAKED_BASE_PATH_FILE="$baked_file" \
sh "$ENTRYPOINT"
grep -F 'location ^~ /skillhub/ {' "$match_config" >/dev/null
printf '%s\n' 'web-base-path-routing-test passed'