#!/bin/sh set -eu # Real container smoke test: runs the actual nginx:alpine entrypoint with the # repo's nginx template + 20-base-path.sh, then verifies over HTTP that a # sub-path deployment serves real assets (not the SPA fallback) and redirects # the bare prefix. This catches routing regressions that a text-only check # cannot (e.g. assets falling through to index.html). if ! command -v docker >/dev/null 2>&1; then printf '%s\n' 'web-base-path-nginx-smoke-test skipped (docker unavailable)' exit 0 fi ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd) NGINX_IMAGE="${NGINX_SMOKE_IMAGE:-nginx:alpine}" name="skillhub-base-path-smoke-$$" port=18080 tmp=$(mktemp -d) cleanup() { docker rm -f "$name" "$name-fixed" >/dev/null 2>&1 || true rm -rf "$tmp" } trap cleanup EXIT html="$tmp/html" mkdir -p "$html/assets" printf '%s\n' 'INDEX_HTML_MARKER' >"$html/index.html" printf '%s\n' 'APP_JS_MARKER' >"$html/assets/app.js" # The image build chmods the entrypoint scripts; here we mount a copy and make it # executable, since the nginx entrypoint silently ignores non-executable *.sh. entrypoint_d="$tmp/entrypoint.d" mkdir -p "$entrypoint_d" cp "$ROOT_DIR/web/docker-entrypoint.d/20-base-path.sh" "$entrypoint_d/20-base-path.sh" chmod +x "$entrypoint_d/20-base-path.sh" if ! docker run -d --name "$name" \ -p "$port:80" \ -e SKILLHUB_API_UPSTREAM=http://127.0.0.1:9 \ -e SKILLHUB_TRUST_FORWARDED_PROTO=false \ -e SKILLHUB_WEB_BASE_PATH=/skillhub/ \ -v "$html:/usr/share/nginx/html:ro" \ -v "$ROOT_DIR/web/nginx.conf.template:/etc/nginx/templates/default.conf.template:ro" \ -v "$entrypoint_d/20-base-path.sh:/docker-entrypoint.d/20-base-path.sh:ro" \ "$NGINX_IMAGE" >/dev/null 2>&1; then printf '%s\n' 'web-base-path-nginx-smoke-test skipped (docker run failed, e.g. no image/network)' exit 0 fi base="http://127.0.0.1:$port" ready=0 i=0 while [ "$i" -lt 30 ]; do if curl -fsS -o /dev/null "$base/nginx-health" 2>/dev/null; then ready=1 break fi i=$((i + 1)) sleep 1 done if [ "$ready" -ne 1 ]; then echo 'nginx did not become ready' >&2 docker logs "$name" >&2 || true exit 1 fi # Asset under the sub-path must serve the real file, not the SPA fallback. asset=$(curl -fsS "$base/skillhub/assets/app.js") if [ "$asset" != 'APP_JS_MARKER' ]; then echo "sub-path asset must serve the real file, got: $asset" >&2 exit 1 fi # App route under the sub-path falls back to index.html (SPA). index=$(curl -fsS "$base/skillhub/dashboard") if [ "$index" != 'INDEX_HTML_MARKER' ]; then echo "sub-path SPA route must serve index.html, got: $index" >&2 exit 1 fi # Bare prefix redirects to the trailing-slash form. code=$(curl -s -o /dev/null -w '%{http_code}' "$base/skillhub") if [ "$code" != '301' ]; then echo "bare prefix must 301-redirect, got: $code" >&2 exit 1 fi docker rm -f "$name" >/dev/null 2>&1 || true # Fixed-base image served via the bundled deploy configs: assets are baked under # /fixed/, a baked-base marker is present, and SKILLHUB_WEB_BASE_PATH is passed as # an empty string (as compose.release.yml / k8s do). Routing must follow the baked # base, not fall back to root. Reproduces the reported P1 regression. fixed_html="$tmp/fixed-html" mkdir -p "$fixed_html/assets" printf '%s\n' 'INDEX_HTML_MARKER' >"$fixed_html/index.html" printf '%s\n' 'FIXED_APP_JS_MARKER' >"$fixed_html/assets/app.js" baked_file="$tmp/baked-base-path" printf '%s' '/fixed/' >"$baked_file" fixed_name="$name-fixed" fixed_port=18081 docker run -d --name "$fixed_name" \ -p "$fixed_port:80" \ -e SKILLHUB_API_UPSTREAM=http://127.0.0.1:9 \ -e SKILLHUB_TRUST_FORWARDED_PROTO=false \ -e SKILLHUB_WEB_BASE_PATH= \ -e SKILLHUB_WEB_BAKED_BASE_PATH_FILE=/etc/skillhub/baked-base-path \ -v "$fixed_html:/usr/share/nginx/html:ro" \ -v "$baked_file:/etc/skillhub/baked-base-path:ro" \ -v "$ROOT_DIR/web/nginx.conf.template:/etc/nginx/templates/default.conf.template:ro" \ -v "$entrypoint_d/20-base-path.sh:/docker-entrypoint.d/20-base-path.sh:ro" \ "$NGINX_IMAGE" >/dev/null 2>&1 fixed_base="http://127.0.0.1:$fixed_port" ready=0 i=0 while [ "$i" -lt 30 ]; do if curl -fsS -o /dev/null "$fixed_base/nginx-health" 2>/dev/null; then ready=1 break fi i=$((i + 1)) sleep 1 done if [ "$ready" -ne 1 ]; then echo 'nginx (fixed-base) did not become ready' >&2 docker logs "$fixed_name" >&2 || true exit 1 fi fixed_asset=$(curl -fsS "$fixed_base/fixed/assets/app.js") if [ "$fixed_asset" != 'FIXED_APP_JS_MARKER' ]; then echo "fixed-base asset must serve the real file, got: $fixed_asset" >&2 exit 1 fi printf '%s\n' 'web-base-path-nginx-smoke-test passed'