3 changed files with 858 additions and 0 deletions
317
.woodpecker/edge-subpath.yml
Normal file
317
.woodpecker/edge-subpath.yml
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
# =============================================================================
|
||||
# .woodpecker/edge-subpath.yml — Edge subpath routing static checks
|
||||
#
|
||||
# Static validation for edge subpath routing configuration. This pipeline does
|
||||
# NOT run live service curls — it validates the configuration that would be
|
||||
# used by a deployed edge proxy.
|
||||
#
|
||||
# Checks:
|
||||
# 1. shellcheck — syntax check on tests/smoke-edge-subpath.sh
|
||||
# 2. caddy validate — validate the Caddyfile template syntax
|
||||
# 3. caddyfile-routing-test — verify Caddyfile routing block shape
|
||||
# 4. test-caddyfile-routing — run standalone unit test for Caddyfile structure
|
||||
#
|
||||
# Triggers:
|
||||
# - Pull requests that modify edge-related files
|
||||
#
|
||||
# Environment variables (inherited from WOODPECKER_ENVIRONMENT):
|
||||
# EDGE_BASE_URL — Edge proxy URL for reference (default: http://localhost)
|
||||
# EDGE_TIMEOUT — Request timeout in seconds (default: 30)
|
||||
# EDGE_MAX_RETRIES — Max retries per request (default: 3)
|
||||
# =============================================================================
|
||||
|
||||
when:
|
||||
event: pull_request
|
||||
|
||||
steps:
|
||||
# ── 1. ShellCheck on smoke script ────────────────────────────────────────
|
||||
# `shellcheck` validates bash syntax, style, and common pitfalls.
|
||||
# Exit codes:
|
||||
# 0 — all checks passed
|
||||
# 1 — one or more issues found
|
||||
- name: shellcheck-smoke
|
||||
image: koalaman/shellcheck-alpine:stable
|
||||
commands:
|
||||
- shellcheck --severity=warning tests/smoke-edge-subpath.sh tests/test-caddyfile-routing.sh
|
||||
|
||||
# ── 2. Caddyfile template rendering ───────────────────────────────────────
|
||||
# Render a mock Caddyfile for validation. The template uses Nomad's
|
||||
# templating syntax ({{ range ... }}) which must be processed before Caddy
|
||||
# can validate it. We render a mock version with Nomad templates expanded
|
||||
# to static values for validation purposes.
|
||||
- name: render-caddyfile
|
||||
image: alpine:3.19
|
||||
commands:
|
||||
- apk add --no-cache coreutils
|
||||
- |
|
||||
set -e
|
||||
mkdir -p edge-render
|
||||
# Render mock Caddyfile with Nomad templates expanded
|
||||
{
|
||||
echo '# Caddyfile — edge proxy configuration (Nomad-rendered)'
|
||||
echo '# Staging upstream discovered via Nomad service registration.'
|
||||
echo ''
|
||||
echo ':80 {'
|
||||
echo ' # Redirect root to Forgejo'
|
||||
echo ' handle / {'
|
||||
echo ' redir /forge/ 302'
|
||||
echo ' }'
|
||||
echo ''
|
||||
echo ' # Reverse proxy to Forgejo'
|
||||
echo ' handle /forge/* {'
|
||||
echo ' reverse_proxy 127.0.0.1:3000'
|
||||
echo ' }'
|
||||
echo ''
|
||||
echo ' # Reverse proxy to Woodpecker CI'
|
||||
echo ' handle /ci/* {'
|
||||
echo ' reverse_proxy 127.0.0.1:8000'
|
||||
echo ' }'
|
||||
echo ''
|
||||
echo ' # Reverse proxy to staging — dynamic port via Nomad service discovery'
|
||||
echo ' handle /staging/* {'
|
||||
echo ' reverse_proxy 127.0.0.1:8081'
|
||||
echo ' }'
|
||||
echo ''
|
||||
echo ' # Chat service — reverse proxy to disinto-chat backend (#705)'
|
||||
echo ' # OAuth routes bypass forward_auth — unauthenticated users need these (#709)'
|
||||
echo ' handle /chat/login {'
|
||||
echo ' reverse_proxy 127.0.0.1:8080'
|
||||
echo ' }'
|
||||
echo ' handle /chat/oauth/callback {'
|
||||
echo ' reverse_proxy 127.0.0.1:8080'
|
||||
echo ' }'
|
||||
echo ' # Defense-in-depth: forward_auth stamps X-Forwarded-User from session (#709)'
|
||||
echo ' handle /chat/* {'
|
||||
echo ' forward_auth 127.0.0.1:8080 {'
|
||||
echo ' uri /chat/auth/verify'
|
||||
echo ' copy_headers X-Forwarded-User'
|
||||
echo ' header_up X-Forward-Auth-Secret {$FORWARD_AUTH_SECRET}'
|
||||
echo ' }'
|
||||
echo ' reverse_proxy 127.0.0.1:8080'
|
||||
echo ' }'
|
||||
echo '}'
|
||||
} > edge-render/Caddyfile
|
||||
cp edge-render/Caddyfile edge-render/Caddyfile.rendered
|
||||
echo "Caddyfile rendered successfully"
|
||||
|
||||
# ── 3. Caddy config validation ───────────────────────────────────────────
|
||||
# `caddy validate` checks Caddyfile syntax and configuration.
|
||||
# This validates the rendered Caddyfile against Caddy's parser.
|
||||
# Exit codes:
|
||||
# 0 — configuration is valid
|
||||
# 1 — configuration has errors
|
||||
- name: caddy-validate
|
||||
image: alpine:3.19
|
||||
commands:
|
||||
- apk add --no-cache ca-certificates curl
|
||||
- curl -sS -o /tmp/caddy "https://caddyserver.com/api/download?os=linux&arch=amd64"
|
||||
- chmod +x /tmp/caddy
|
||||
- /tmp/caddy version
|
||||
- /tmp/caddy validate --config edge-render/Caddyfile.rendered --adapter caddyfile
|
||||
|
||||
# ── 4. Caddyfile routing block shape test ─────────────────────────────────
|
||||
# Verify that the Caddyfile contains all required routing blocks:
|
||||
# - /forge/ — Forgejo subpath
|
||||
# - /ci/ — Woodpecker subpath
|
||||
# - /staging/ — Staging subpath
|
||||
# - /chat/ — Chat subpath with forward_auth
|
||||
#
|
||||
# This is a unit test that validates the expected structure without
|
||||
# requiring a running Caddy instance.
|
||||
- name: caddyfile-routing-test
|
||||
image: alpine:3.19
|
||||
commands:
|
||||
- apk add --no-cache grep coreutils
|
||||
- |
|
||||
set -e
|
||||
|
||||
CADDYFILE="edge-render/Caddyfile.rendered"
|
||||
|
||||
echo "=== Validating Caddyfile routing blocks ==="
|
||||
|
||||
# Check that all required subpath handlers exist
|
||||
# POSIX-safe loop (alpine /bin/sh has no arrays)
|
||||
FAILED=0
|
||||
for handler in "handle /forge/\*" "handle /ci/\*" "handle /staging/\*" "handle /chat/login" "handle /chat/oauth/callback" "handle /chat/\*"; do
|
||||
if grep -q "$handler" "$CADDYFILE"; then
|
||||
echo "[PASS] Found handler: $handler"
|
||||
else
|
||||
echo "[FAIL] Missing handler: $handler"
|
||||
FAILED=1
|
||||
fi
|
||||
done
|
||||
|
||||
# Check forward_auth block exists for /chat/*
|
||||
if grep -A5 "handle /chat/\*" "$CADDYFILE" | grep -q "forward_auth"; then
|
||||
echo "[PASS] forward_auth block found for /chat/*"
|
||||
else
|
||||
echo "[FAIL] forward_auth block missing for /chat/*"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
# Check reverse_proxy to Forgejo (port 3000)
|
||||
if grep -q "reverse_proxy 127.0.0.1:3000" "$CADDYFILE"; then
|
||||
echo "[PASS] Forgejo reverse_proxy configured (port 3000)"
|
||||
else
|
||||
echo "[FAIL] Forgejo reverse_proxy not configured"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
# Check reverse_proxy to Woodpecker (port 8000)
|
||||
if grep -q "reverse_proxy 127.0.0.1:8000" "$CADDYFILE"; then
|
||||
echo "[PASS] Woodpecker reverse_proxy configured (port 8000)"
|
||||
else
|
||||
echo "[FAIL] Woodpecker reverse_proxy not configured"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
# Check reverse_proxy to Chat (port 8080)
|
||||
if grep -q "reverse_proxy 127.0.0.1:8080" "$CADDYFILE"; then
|
||||
echo "[PASS] Chat reverse_proxy configured (port 8080)"
|
||||
else
|
||||
echo "[FAIL] Chat reverse_proxy not configured"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
# Check root redirect to /forge/
|
||||
if grep -q "redir /forge/ 302" "$CADDYFILE"; then
|
||||
echo "[PASS] Root redirect to /forge/ configured"
|
||||
else
|
||||
echo "[FAIL] Root redirect to /forge/ not configured"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if [ $FAILED -eq 0 ]; then
|
||||
echo "=== All routing blocks validated ==="
|
||||
exit 0
|
||||
else
|
||||
echo "=== Routing block validation failed ===" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── 5. Standalone Caddyfile routing test ─────────────────────────────────
|
||||
# Run the standalone unit test for Caddyfile routing block validation.
|
||||
# This test extracts the Caddyfile template from edge.hcl and validates
|
||||
# its structure without requiring a running Caddy instance.
|
||||
- name: test-caddyfile-routing
|
||||
image: alpine:3.19
|
||||
commands:
|
||||
- apk add --no-cache grep coreutils
|
||||
- |
|
||||
set -e
|
||||
EDGE_TEMPLATE="nomad/jobs/edge.hcl"
|
||||
|
||||
echo "=== Extracting Caddyfile template from $EDGE_TEMPLATE ==="
|
||||
|
||||
# Extract the Caddyfile template (content between <<EOT and EOT markers)
|
||||
CADDYFILE=$(sed -n '/data[[:space:]]*=[[:space:]]*<<[Ee][Oo][Tt]/,/^EOT$/p' "$EDGE_TEMPLATE" | sed '1s/.*/# Caddyfile extracted from Nomad template/; $d')
|
||||
|
||||
if [ -z "$CADDYFILE" ]; then
|
||||
echo "ERROR: Could not extract Caddyfile template from $EDGE_TEMPLATE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Caddyfile template extracted successfully"
|
||||
echo ""
|
||||
|
||||
FAILED=0
|
||||
|
||||
# Check Forgejo subpath
|
||||
if echo "$CADDYFILE" | grep -q "handle /forge/\*"; then
|
||||
echo "[PASS] Forgejo handle block"
|
||||
else
|
||||
echo "[FAIL] Forgejo handle block"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
if echo "$CADDYFILE" | grep -q "reverse_proxy 127.0.0.1:3000"; then
|
||||
echo "[PASS] Forgejo reverse_proxy (port 3000)"
|
||||
else
|
||||
echo "[FAIL] Forgejo reverse_proxy (port 3000)"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
# Check Woodpecker subpath
|
||||
if echo "$CADDYFILE" | grep -q "handle /ci/\*"; then
|
||||
echo "[PASS] Woodpecker handle block"
|
||||
else
|
||||
echo "[FAIL] Woodpecker handle block"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
if echo "$CADDYFILE" | grep -q "reverse_proxy 127.0.0.1:8000"; then
|
||||
echo "[PASS] Woodpecker reverse_proxy (port 8000)"
|
||||
else
|
||||
echo "[FAIL] Woodpecker reverse_proxy (port 8000)"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
# Check Staging subpath
|
||||
if echo "$CADDYFILE" | grep -q "handle /staging/\*"; then
|
||||
echo "[PASS] Staging handle block"
|
||||
else
|
||||
echo "[FAIL] Staging handle block"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
if echo "$CADDYFILE" | grep -q "nomadService"; then
|
||||
echo "[PASS] Staging Nomad service discovery"
|
||||
else
|
||||
echo "[FAIL] Staging Nomad service discovery"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
# Check Chat subpath
|
||||
if echo "$CADDYFILE" | grep -q "handle /chat/login"; then
|
||||
echo "[PASS] Chat login handle block"
|
||||
else
|
||||
echo "[FAIL] Chat login handle block"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
if echo "$CADDYFILE" | grep -q "handle /chat/oauth/callback"; then
|
||||
echo "[PASS] Chat OAuth callback handle block"
|
||||
else
|
||||
echo "[FAIL] Chat OAuth callback handle block"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
if echo "$CADDYFILE" | grep -q "handle /chat/\*"; then
|
||||
echo "[PASS] Chat catch-all handle block"
|
||||
else
|
||||
echo "[FAIL] Chat catch-all handle block"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
if echo "$CADDYFILE" | grep -q "reverse_proxy 127.0.0.1:8080"; then
|
||||
echo "[PASS] Chat reverse_proxy (port 8080)"
|
||||
else
|
||||
echo "[FAIL] Chat reverse_proxy (port 8080)"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
# Check forward_auth for chat
|
||||
if echo "$CADDYFILE" | grep -A10 "handle /chat/\*" | grep -q "forward_auth"; then
|
||||
echo "[PASS] forward_auth block for /chat/*"
|
||||
else
|
||||
echo "[FAIL] forward_auth block for /chat/*"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
# Check root redirect
|
||||
if echo "$CADDYFILE" | grep -q "redir /forge/ 302"; then
|
||||
echo "[PASS] Root redirect to /forge/"
|
||||
else
|
||||
echo "[FAIL] Root redirect to /forge/"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if [ $FAILED -eq 0 ]; then
|
||||
echo "=== All routing blocks validated ==="
|
||||
exit 0
|
||||
else
|
||||
echo "=== Routing block validation failed ===" >&2
|
||||
exit 1
|
||||
fi
|
||||
310
tests/smoke-edge-subpath.sh
Executable file
310
tests/smoke-edge-subpath.sh
Executable file
|
|
@ -0,0 +1,310 @@
|
|||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# smoke-edge-subpath.sh — End-to-end subpath routing smoke test
|
||||
#
|
||||
# Verifies Forgejo, Woodpecker, and chat function correctly under subpaths:
|
||||
# - Forgejo at /forge/
|
||||
# - Woodpecker at /ci/
|
||||
# - Chat at /chat/
|
||||
# - Staging at /staging/
|
||||
#
|
||||
# Usage:
|
||||
# smoke-edge-subpath.sh [--base-url BASE_URL]
|
||||
#
|
||||
# Environment variables:
|
||||
# BASE_URL — Edge proxy URL (default: http://localhost)
|
||||
# EDGE_TIMEOUT — Request timeout in seconds (default: 30)
|
||||
# EDGE_MAX_RETRIES — Max retries per request (default: 3)
|
||||
#
|
||||
# Exit codes:
|
||||
# 0 — All checks passed
|
||||
# 1 — One or more checks failed
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
# Script directory for relative paths
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Source common helpers if available
|
||||
source "${SCRIPT_DIR}/../lib/env.sh" 2>/dev/null || true
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Configuration
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
BASE_URL="${BASE_URL:-http://localhost}"
|
||||
EDGE_TIMEOUT="${EDGE_TIMEOUT:-30}"
|
||||
EDGE_MAX_RETRIES="${EDGE_MAX_RETRIES:-3}"
|
||||
|
||||
# Subpaths to test
|
||||
FORGE_PATH="/forge/"
|
||||
CI_PATH="/ci/"
|
||||
CHAT_PATH="/chat/"
|
||||
STAGING_PATH="/staging/"
|
||||
|
||||
# Track overall test status
|
||||
FAILED=0
|
||||
PASSED=0
|
||||
SKIPPED=0
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Logging helpers
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
log_info() {
|
||||
echo "[INFO] $*"
|
||||
}
|
||||
|
||||
log_pass() {
|
||||
echo "[PASS] $*"
|
||||
((PASSED++)) || true
|
||||
}
|
||||
|
||||
log_fail() {
|
||||
echo "[FAIL] $*"
|
||||
((FAILED++)) || true
|
||||
}
|
||||
|
||||
log_skip() {
|
||||
echo "[SKIP] $*"
|
||||
((SKIPPED++)) || true
|
||||
}
|
||||
|
||||
log_section() {
|
||||
echo ""
|
||||
echo "=== $* ==="
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# HTTP helpers
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
# Make an HTTP request with retry logic
|
||||
# Usage: http_request <method> <url> [options...]
|
||||
# Returns: HTTP status code on stdout
|
||||
http_request() {
|
||||
local method="$1"
|
||||
local url="$2"
|
||||
shift 2
|
||||
|
||||
local retries=0
|
||||
local response status
|
||||
|
||||
while [ "$retries" -lt "$EDGE_MAX_RETRIES" ]; do
|
||||
response=$(curl -sS -w '\n%{http_code}' -X "$method" \
|
||||
--max-time "$EDGE_TIMEOUT" \
|
||||
-o /tmp/edge-response-$$ \
|
||||
"$@" 2>&1) || {
|
||||
retries=$((retries + 1))
|
||||
log_info "Retry $retries/$EDGE_MAX_RETRIES for $url"
|
||||
sleep 1
|
||||
continue
|
||||
}
|
||||
|
||||
status=$(echo "$response" | tail -n1)
|
||||
|
||||
echo "$status"
|
||||
return 0
|
||||
done
|
||||
|
||||
log_fail "Max retries exceeded for $url"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Make a GET request and return status code
|
||||
http_get() {
|
||||
local url="$1"
|
||||
shift || true
|
||||
http_request "GET" "$url" "$@"
|
||||
}
|
||||
|
||||
# Make a HEAD request (no body)
|
||||
http_head() {
|
||||
local url="$1"
|
||||
shift || true
|
||||
http_request "HEAD" "$url" "$@"
|
||||
}
|
||||
|
||||
# Make a GET request and return the response body
|
||||
http_get_body() {
|
||||
local url="$1"
|
||||
shift || true
|
||||
curl -sS --max-time "$EDGE_TIMEOUT" "$@" "$url"
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Test functions
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
test_root_redirect() {
|
||||
log_section "Test 1: Root redirect to /forge/"
|
||||
|
||||
local status
|
||||
status=$(http_head "$BASE_URL/")
|
||||
|
||||
if [ "$status" = "302" ]; then
|
||||
log_pass "Root / redirects with 302"
|
||||
else
|
||||
log_fail "Expected 302 redirect from /, got status $status"
|
||||
fi
|
||||
}
|
||||
|
||||
test_forgejo_subpath() {
|
||||
log_section "Test 2: Forgejo at /forge/"
|
||||
|
||||
local status
|
||||
status=$(http_head "$BASE_URL${FORGE_PATH}")
|
||||
|
||||
if [ "$status" -ge 200 ] && [ "$status" -lt 400 ]; then
|
||||
log_pass "Forgejo at ${BASE_URL}${FORGE_PATH} returns status $status"
|
||||
else
|
||||
log_fail "Forgejo at ${BASE_URL}${FORGE_PATH} returned unexpected status $status"
|
||||
fi
|
||||
}
|
||||
|
||||
test_woodpecker_subpath() {
|
||||
log_section "Test 3: Woodpecker at /ci/"
|
||||
|
||||
local status
|
||||
status=$(http_head "$BASE_URL${CI_PATH}")
|
||||
|
||||
if [ "$status" -ge 200 ] && [ "$status" -lt 400 ]; then
|
||||
log_pass "Woodpecker at ${BASE_URL}${CI_PATH} returns status $status"
|
||||
else
|
||||
log_fail "Woodpecker at ${BASE_URL}${CI_PATH} returned unexpected status $status"
|
||||
fi
|
||||
}
|
||||
|
||||
test_chat_subpath() {
|
||||
log_section "Test 4: Chat at /chat/"
|
||||
|
||||
# Test chat login endpoint
|
||||
local status
|
||||
status=$(http_head "$BASE_URL${CHAT_PATH}login")
|
||||
|
||||
if [ "$status" -ge 200 ] && [ "$status" -lt 400 ]; then
|
||||
log_pass "Chat login at ${BASE_URL}${CHAT_PATH}login returns status $status"
|
||||
else
|
||||
log_fail "Chat login at ${BASE_URL}${CHAT_PATH}login returned unexpected status $status"
|
||||
fi
|
||||
|
||||
# Test chat OAuth callback endpoint
|
||||
status=$(http_head "$BASE_URL${CHAT_PATH}oauth/callback")
|
||||
|
||||
if [ "$status" -ge 200 ] && [ "$status" -lt 400 ]; then
|
||||
log_pass "Chat OAuth callback at ${BASE_URL}${CHAT_PATH}oauth/callback returns status $status"
|
||||
else
|
||||
log_fail "Chat OAuth callback at ${BASE_URL}${CHAT_PATH}oauth/callback returned unexpected status $status"
|
||||
fi
|
||||
}
|
||||
|
||||
test_staging_subpath() {
|
||||
log_section "Test 5: Staging at /staging/"
|
||||
|
||||
local status
|
||||
status=$(http_head "$BASE_URL${STAGING_PATH}")
|
||||
|
||||
if [ "$status" -ge 200 ] && [ "$status" -lt 400 ]; then
|
||||
log_pass "Staging at ${BASE_URL}${STAGING_PATH} returns status $status"
|
||||
else
|
||||
log_fail "Staging at ${BASE_URL}${STAGING_PATH} returned unexpected status $status"
|
||||
fi
|
||||
}
|
||||
|
||||
test_forward_auth_rejection() {
|
||||
log_section "Test 6: Forward auth on /chat/* rejects unauthenticated requests"
|
||||
|
||||
# Request a protected chat endpoint without auth header
|
||||
# Should return 401 (Unauthorized) due to forward_auth
|
||||
local status
|
||||
status=$(http_head "$BASE_URL${CHAT_PATH}auth/verify")
|
||||
|
||||
if [ "$status" = "401" ]; then
|
||||
log_pass "Unauthenticated /chat/auth/verify returns 401 (forward_auth working)"
|
||||
elif [ "$status" -ge 200 ] && [ "$status" -lt 400 ]; then
|
||||
log_skip "Unauthenticated /chat/auth/verify returns $status (forward_auth may be disabled)"
|
||||
else
|
||||
log_fail "Expected 401 for unauthenticated /chat/auth/verify, got status $status"
|
||||
fi
|
||||
}
|
||||
|
||||
test_forgejo_oauth_callback() {
|
||||
log_section "Test 7: Forgejo OAuth callback for Woodpecker under subpath"
|
||||
|
||||
# Test that Forgejo OAuth callback path works (Woodpecker OAuth integration)
|
||||
local status
|
||||
status=$(http_head "$BASE_URL${FORGE_PATH}login/oauth/callback")
|
||||
|
||||
if [ "$status" -ge 200 ] && [ "$status" -lt 400 ]; then
|
||||
log_pass "Forgejo OAuth callback at ${BASE_URL}${FORGE_PATH}login/oauth/callback works"
|
||||
else
|
||||
log_fail "Forgejo OAuth callback returned unexpected status $status"
|
||||
fi
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Main
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
main() {
|
||||
log_info "Starting subpath routing smoke test"
|
||||
log_info "Base URL: $BASE_URL"
|
||||
log_info "Timeout: ${EDGE_TIMEOUT}s, Max retries: ${EDGE_MAX_RETRIES}"
|
||||
|
||||
# Run all tests
|
||||
test_root_redirect
|
||||
test_forgejo_subpath
|
||||
test_woodpecker_subpath
|
||||
test_chat_subpath
|
||||
test_staging_subpath
|
||||
test_forward_auth_rejection
|
||||
test_forgejo_oauth_callback
|
||||
|
||||
# Summary
|
||||
log_section "Test Summary"
|
||||
log_info "Passed: $PASSED"
|
||||
log_info "Failed: $FAILED"
|
||||
log_info "Skipped: $SKIPPED"
|
||||
|
||||
if [ "$FAILED" -gt 0 ]; then
|
||||
log_fail "Some tests failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_pass "All tests passed!"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--base-url)
|
||||
BASE_URL="$2"
|
||||
shift 2
|
||||
;;
|
||||
--base-url=*)
|
||||
BASE_URL="${1#*=}"
|
||||
shift
|
||||
;;
|
||||
--help)
|
||||
echo "Usage: $0 [options]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --base-url URL Set base URL (default: http://localhost)"
|
||||
echo " --help Show this help message"
|
||||
echo ""
|
||||
echo "Environment variables:"
|
||||
echo " BASE_URL Base URL for edge proxy (default: http://localhost)"
|
||||
echo " EDGE_TIMEOUT Request timeout in seconds (default: 30)"
|
||||
echo " EDGE_MAX_RETRIES Max retries per request (default: 3)"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
main
|
||||
231
tests/test-caddyfile-routing.sh
Executable file
231
tests/test-caddyfile-routing.sh
Executable file
|
|
@ -0,0 +1,231 @@
|
|||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# test-caddyfile-routing.sh — Caddyfile routing block unit test
|
||||
#
|
||||
# Extracts the Caddyfile template from nomad/jobs/edge.hcl and validates its
|
||||
# structure without requiring a running Caddy instance.
|
||||
#
|
||||
# Checks:
|
||||
# - Forgejo subpath (/forge/* -> :3000)
|
||||
# - Woodpecker subpath (/ci/* -> :8000)
|
||||
# - Staging subpath (/staging/* -> nomadService discovery)
|
||||
# - Chat subpath (/chat/* with forward_auth and OAuth routes)
|
||||
# - Root redirect to /forge/
|
||||
#
|
||||
# Usage:
|
||||
# test-caddyfile-routing.sh
|
||||
#
|
||||
# Exit codes:
|
||||
# 0 — All checks passed
|
||||
# 1 — One or more checks failed
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
# Script directory for relative paths
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
|
||||
EDGE_TEMPLATE="${REPO_ROOT}/nomad/jobs/edge.hcl"
|
||||
|
||||
# Track test status
|
||||
FAILED=0
|
||||
PASSED=0
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Logging helpers
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
tr_info() {
|
||||
echo "[INFO] $*"
|
||||
}
|
||||
|
||||
tr_pass() {
|
||||
echo "[PASS] $*"
|
||||
((PASSED++)) || true
|
||||
}
|
||||
|
||||
tr_fail() {
|
||||
echo "[FAIL] $*"
|
||||
((FAILED++)) || true
|
||||
}
|
||||
|
||||
tr_section() {
|
||||
echo ""
|
||||
echo "=== $* ==="
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Caddyfile extraction
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
extract_caddyfile() {
|
||||
local template_file="$1"
|
||||
|
||||
# Extract the Caddyfile template (content between <<EOT and EOT markers
|
||||
# within the template stanza)
|
||||
local caddyfile
|
||||
caddyfile=$(sed -n '/data[[:space:]]*=[[:space:]]*<<[Ee][Oo][Tt]/,/^EOT$/p' "$template_file" | sed '1s/.*/# Caddyfile extracted from Nomad template/; $d')
|
||||
|
||||
if [ -z "$caddyfile" ]; then
|
||||
echo "ERROR: Could not extract Caddyfile template from $template_file" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$caddyfile"
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Validation functions
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
check_forgejo_routing() {
|
||||
tr_section "Validating Forgejo routing"
|
||||
|
||||
# Check handle block for /forge/*
|
||||
if echo "$CADDYFILE" | grep -q "handle /forge/\*"; then
|
||||
tr_pass "Forgejo handle block (handle /forge/*)"
|
||||
else
|
||||
tr_fail "Missing Forgejo handle block (handle /forge/*)"
|
||||
fi
|
||||
|
||||
# Check reverse_proxy to Forgejo on port 3000
|
||||
if echo "$CADDYFILE" | grep -q "reverse_proxy 127.0.0.1:3000"; then
|
||||
tr_pass "Forgejo reverse_proxy configured (127.0.0.1:3000)"
|
||||
else
|
||||
tr_fail "Missing Forgejo reverse_proxy (127.0.0.1:3000)"
|
||||
fi
|
||||
}
|
||||
|
||||
check_woodpecker_routing() {
|
||||
tr_section "Validating Woodpecker routing"
|
||||
|
||||
# Check handle block for /ci/*
|
||||
if echo "$CADDYFILE" | grep -q "handle /ci/\*"; then
|
||||
tr_pass "Woodpecker handle block (handle /ci/*)"
|
||||
else
|
||||
tr_fail "Missing Woodpecker handle block (handle /ci/*)"
|
||||
fi
|
||||
|
||||
# Check reverse_proxy to Woodpecker on port 8000
|
||||
if echo "$CADDYFILE" | grep -q "reverse_proxy 127.0.0.1:8000"; then
|
||||
tr_pass "Woodpecker reverse_proxy configured (127.0.0.1:8000)"
|
||||
else
|
||||
tr_fail "Missing Woodpecker reverse_proxy (127.0.0.1:8000)"
|
||||
fi
|
||||
}
|
||||
|
||||
check_staging_routing() {
|
||||
tr_section "Validating Staging routing"
|
||||
|
||||
# Check handle block for /staging/*
|
||||
if echo "$CADDYFILE" | grep -q "handle /staging/\*"; then
|
||||
tr_pass "Staging handle block (handle /staging/*)"
|
||||
else
|
||||
tr_fail "Missing Staging handle block (handle /staging/*)"
|
||||
fi
|
||||
|
||||
# Check for nomadService discovery (dynamic port)
|
||||
if echo "$CADDYFILE" | grep -q "nomadService"; then
|
||||
tr_pass "Staging uses Nomad service discovery"
|
||||
else
|
||||
tr_fail "Missing Nomad service discovery for staging"
|
||||
fi
|
||||
}
|
||||
|
||||
check_chat_routing() {
|
||||
tr_section "Validating Chat routing"
|
||||
|
||||
# Check login endpoint
|
||||
if echo "$CADDYFILE" | grep -q "handle /chat/login"; then
|
||||
tr_pass "Chat login handle block (handle /chat/login)"
|
||||
else
|
||||
tr_fail "Missing Chat login handle block (handle /chat/login)"
|
||||
fi
|
||||
|
||||
# Check OAuth callback endpoint
|
||||
if echo "$CADDYFILE" | grep -q "handle /chat/oauth/callback"; then
|
||||
tr_pass "Chat OAuth callback handle block (handle /chat/oauth/callback)"
|
||||
else
|
||||
tr_fail "Missing Chat OAuth callback handle block (handle /chat/oauth/callback)"
|
||||
fi
|
||||
|
||||
# Check catch-all for /chat/*
|
||||
if echo "$CADDYFILE" | grep -q "handle /chat/\*"; then
|
||||
tr_pass "Chat catch-all handle block (handle /chat/*)"
|
||||
else
|
||||
tr_fail "Missing Chat catch-all handle block (handle /chat/*)"
|
||||
fi
|
||||
|
||||
# Check reverse_proxy to Chat on port 8080
|
||||
if echo "$CADDYFILE" | grep -q "reverse_proxy 127.0.0.1:8080"; then
|
||||
tr_pass "Chat reverse_proxy configured (127.0.0.1:8080)"
|
||||
else
|
||||
tr_fail "Missing Chat reverse_proxy (127.0.0.1:8080)"
|
||||
fi
|
||||
|
||||
# Check forward_auth block for /chat/*
|
||||
if echo "$CADDYFILE" | grep -A10 "handle /chat/\*" | grep -q "forward_auth"; then
|
||||
tr_pass "forward_auth block configured for /chat/*"
|
||||
else
|
||||
tr_fail "Missing forward_auth block for /chat/*"
|
||||
fi
|
||||
|
||||
# Check forward_auth URI
|
||||
if echo "$CADDYFILE" | grep -q "uri /chat/auth/verify"; then
|
||||
tr_pass "forward_auth URI configured (/chat/auth/verify)"
|
||||
else
|
||||
tr_fail "Missing forward_auth URI (/chat/auth/verify)"
|
||||
fi
|
||||
}
|
||||
|
||||
check_root_redirect() {
|
||||
tr_section "Validating root redirect"
|
||||
|
||||
# Check root redirect to /forge/
|
||||
if echo "$CADDYFILE" | grep -q "redir /forge/ 302"; then
|
||||
tr_pass "Root redirect to /forge/ configured (302)"
|
||||
else
|
||||
tr_fail "Missing root redirect to /forge/"
|
||||
fi
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Main
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
main() {
|
||||
tr_info "Extracting Caddyfile template from $EDGE_TEMPLATE"
|
||||
|
||||
# Extract Caddyfile
|
||||
CADDYFILE=$(extract_caddyfile "$EDGE_TEMPLATE")
|
||||
|
||||
if [ -z "$CADDYFILE" ]; then
|
||||
tr_fail "Could not extract Caddyfile template"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tr_pass "Caddyfile template extracted successfully"
|
||||
|
||||
# Run all validation checks
|
||||
check_forgejo_routing
|
||||
check_woodpecker_routing
|
||||
check_staging_routing
|
||||
check_chat_routing
|
||||
check_root_redirect
|
||||
|
||||
# Summary
|
||||
tr_section "Test Summary"
|
||||
tr_info "Passed: $PASSED"
|
||||
tr_info "Failed: $FAILED"
|
||||
|
||||
if [ "$FAILED" -gt 0 ]; then
|
||||
tr_fail "Some checks failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tr_pass "All routing blocks validated!"
|
||||
exit 0
|
||||
}
|
||||
|
||||
main
|
||||
Loading…
Add table
Add a link
Reference in a new issue