Files
PolyWeather/scripts/validate_frontend_cache.sh
2026-06-30 13:22:11 +08:00

266 lines
6.5 KiB
Bash

#!/usr/bin/env bash
set -u
BASE_URL="${1:-https://polyweather.top}"
CURL_BIN="${CURL_BIN:-curl}"
REQUIRE_CF_CACHE="${REQUIRE_CF_CACHE:-false}"
PASS_COUNT=0
FAIL_COUNT=0
print_line() {
printf '%s\n' "$1"
}
pass() {
PASS_COUNT=$((PASS_COUNT + 1))
print_line "PASS: $1"
}
fail() {
FAIL_COUNT=$((FAIL_COUNT + 1))
print_line "FAIL: $1"
}
header_dump() {
local url="$1"
"$CURL_BIN" -sS -D - -o /dev/null "$url"
}
status_code() {
local headers="$1"
printf '%s\n' "$headers" | awk 'NR==1 { print $2 }'
}
header_value() {
local headers="$1"
local key="$2"
printf '%s\n' "$headers" \
| tr -d '\r' \
| awk -F': ' -v k="$key" 'tolower($1)==tolower(k) { print $2; exit }'
}
cf_cache_status() {
local headers="$1"
header_value "$headers" "CF-Cache-Status"
}
contains_ci() {
local haystack="$1"
local needle="$2"
printf '%s' "$haystack" | grep -qi -- "$needle"
}
check_reachable() {
local url="${BASE_URL%/}/api/cities"
local headers
if ! headers="$(header_dump "$url")"; then
fail "cannot connect to $url"
return 1
fi
local code
code="$(status_code "$headers")"
if [ "$code" = "200" ] || [ "$code" = "304" ]; then
pass "service reachable at $BASE_URL"
return 0
fi
fail "service reachable but unexpected status for /api/cities: $code"
return 1
}
check_cached_endpoint() {
local endpoint="$1"
local label="$2"
local url="${BASE_URL%/}${endpoint}"
local headers
if ! headers="$(header_dump "$url")"; then
fail "$label request failed: $url"
return
fi
local code cache_control etag
code="$(status_code "$headers")"
cache_control="$(header_value "$headers" "Cache-Control")"
etag="$(header_value "$headers" "ETag")"
if [ "$code" != "200" ]; then
fail "$label status expected 200, got $code"
else
pass "$label status 200"
fi
if [ -n "$cache_control" ] && ! contains_ci "$cache_control" "no-store"; then
pass "$label Cache-Control is cacheable: $cache_control"
else
fail "$label Cache-Control invalid: ${cache_control:-<empty>}"
fi
if [ -n "$etag" ]; then
pass "$label has ETag: $etag"
else
fail "$label missing ETag"
fi
}
check_cloudflare_cache_hit() {
local endpoint="$1"
local label="$2"
local url="${BASE_URL%/}${endpoint}"
local headers1 headers2
if ! headers1="$(header_dump "$url")"; then
fail "$label first Cloudflare cache request failed: $url"
return
fi
if ! headers2="$(header_dump "$url")"; then
fail "$label second Cloudflare cache request failed: $url"
return
fi
local code2 status1 status2
code2="$(status_code "$headers2")"
status1="$(cf_cache_status "$headers1")"
status2="$(cf_cache_status "$headers2")"
if [ "$code2" != "200" ]; then
fail "$label Cloudflare cache status check expected HTTP 200, got $code2"
return
fi
case "$status1" in
HIT|MISS|REVALIDATED|STALE|UPDATING|EXPIRED)
pass "$label Cloudflare first status observable: $status1"
;;
"")
if [ "$REQUIRE_CF_CACHE" = "true" ]; then
fail "$label missing CF-Cache-Status on first request"
else
pass "$label CF-Cache-Status unavailable; set REQUIRE_CF_CACHE=true to enforce"
fi
;;
*)
if [ "$REQUIRE_CF_CACHE" = "true" ]; then
fail "$label unexpected first CF-Cache-Status: $status1"
else
pass "$label Cloudflare first status non-cacheable but observed: $status1"
fi
;;
esac
case "$status2" in
HIT|REVALIDATED)
pass "$label Cloudflare edge cache hit: $status2"
;;
MISS)
if [ "$REQUIRE_CF_CACHE" = "true" ]; then
fail "$label Cloudflare edge cache expected HIT or REVALIDATED, got MISS"
else
pass "$label Cloudflare returned MISS; cache rule may still be warming"
fi
;;
"")
if [ "$REQUIRE_CF_CACHE" = "true" ]; then
fail "$label missing CF-Cache-Status on second request"
else
pass "$label CF-Cache-Status missing on second request; enforcement disabled"
fi
;;
*)
if [ "$REQUIRE_CF_CACHE" = "true" ]; then
fail "$label Cloudflare edge cache expected HIT or REVALIDATED, got $status2"
else
pass "$label Cloudflare edge cache not enforced, got $status2"
fi
;;
esac
}
check_force_refresh_nostore() {
local endpoint="$1"
local label="$2"
local url="${BASE_URL%/}${endpoint}"
local headers
if ! headers="$(header_dump "$url")"; then
fail "$label request failed: $url"
return
fi
local code cache_control
code="$(status_code "$headers")"
cache_control="$(header_value "$headers" "Cache-Control")"
if [ "$code" != "200" ]; then
fail "$label status expected 200, got $code"
else
pass "$label status 200"
fi
if contains_ci "$cache_control" "no-store"; then
pass "$label Cache-Control is no-store"
else
fail "$label Cache-Control expected no-store, got: ${cache_control:-<empty>}"
fi
}
check_if_none_match_304() {
local endpoint="$1"
local label="$2"
local url="${BASE_URL%/}${endpoint}"
local headers1
if ! headers1="$(header_dump "$url")"; then
fail "$label first request failed: $url"
return
fi
local etag
etag="$(header_value "$headers1" "ETag")"
if [ -z "$etag" ]; then
fail "$label cannot run 304 check: missing ETag"
return
fi
local headers2 code2
if ! headers2="$("$CURL_BIN" -sS -D - -o /dev/null -H "If-None-Match: $etag" "$url")"; then
fail "$label second request failed: $url"
return
fi
code2="$(status_code "$headers2")"
if [ "$code2" = "304" ]; then
pass "$label If-None-Match returns 304"
else
fail "$label expected 304, got $code2"
fi
}
main() {
print_line "=== PolyWeather Frontend Cache Validation ==="
print_line "Base URL: $BASE_URL"
print_line ""
if ! check_reachable; then
print_line ""
print_line "Result: FAIL ($FAIL_COUNT failed, $PASS_COUNT passed)"
exit 1
fi
check_cached_endpoint "/api/cities" "cities"
check_if_none_match_304 "/api/cities" "cities"
check_cloudflare_cache_hit "/api/cities" "cities edge cache"
check_cached_endpoint "/api/city/ankara/summary" "city summary"
check_force_refresh_nostore "/api/city/ankara/summary?force_refresh=true" "city summary force_refresh"
check_cached_endpoint "/api/history/ankara" "history"
check_if_none_match_304 "/api/history/ankara" "history"
print_line ""
if [ "$FAIL_COUNT" -gt 0 ]; then
print_line "Result: FAIL ($FAIL_COUNT failed, $PASS_COUNT passed)"
exit 1
fi
print_line "Result: PASS ($PASS_COUNT passed)"
}
main "$@"