Date: 2026-02-23
Branch: fix/cache-control-headers → develop
PR: #279
Static resources served with Cache-Control: no-cache, no-store, max-age=0, must-revalidate because custom ResourceHandler registrations never set cache headers, and Spring Security fills in aggressive defaults.
export spring_profiles_active=test,jwt-decoder-in-memory,backoffice-jwt-decoder-no-auth,sap-in-memory,email-in-memory
./gradlew :backend:springboot-app:bootRun -x test -x :frontend:dist -x copyFrontend -x copyStorybookUses H2 in-memory DB so no PostgreSQL needed. You may need dummy files under
build/resources/main/public/{theme,portal,backoffice}/if the frontend hasn't been built.
# Theme CSS — served by /wvz-rs/ui/theme/** handler
curl -s -D - -o /dev/null http://localhost:8080/wvz-rs/ui/theme/webbloqs-4-0-4/css/webbloqs.css
# Portal CSS — served by /wvz-rs/ui/** handler
curl -s -D - -o /dev/null http://localhost:8080/wvz-rs/ui/wvz-design.css
# Backoffice theme — served by /wvz-bo/ui/theme/** handler
curl -s -D - -o /dev/null http://localhost:8080/wvz-bo/ui/theme/webbloqs-4-0-4/css/webbloqs.css
# API/health endpoint — should still be no-cache (not a static resource)
curl -s -D - -o /dev/null http://localhost:8080/healthNote: Use
curl -s -D - -o /dev/null, NOTcurl -I. The-Iflag sends a HEAD request, which Spring Security blocks (only GET ispermitAll()).
#!/usr/bin/env bash
# test-cache-headers.sh — Usage: ./test-cache-headers.sh [base_url]
set -uo pipefail
BASE="${1:-http://localhost:8080}"
PASS=0
FAIL=0
assert_cache_header() {
local url="$1"
local expected="$2"
local label="$3"
local headers
headers=$(curl -s -D - -o /dev/null "$url" 2>/dev/null)
local status
status=$(echo "$headers" | head -1 | tr -d '\r')
local actual
actual=$(echo "$headers" | grep -i "^cache-control:" | sed 's/^[Cc]ache-[Cc]ontrol: //' | tr -d '\r')
if [[ "$actual" == "$expected" ]]; then
echo "PASS: $label"
echo " $url"
echo " $status → Cache-Control: $actual"
PASS=$((PASS + 1))
else
echo "FAIL: $label"
echo " $url"
echo " $status"
echo " expected: Cache-Control: $expected"
echo " actual: Cache-Control: $actual"
FAIL=$((FAIL + 1))
fi
echo
}
echo "=== Cache-Control Header Tests ==="
echo " Base URL: $BASE"
echo
assert_cache_header \
"$BASE/wvz-rs/ui/theme/webbloqs-4-0-4/css/webbloqs.css" \
"max-age=300, public" \
"Theme CSS (webbloqs) should be cached 5 min"
assert_cache_header \
"$BASE/wvz-rs/ui/wvz-design.css" \
"max-age=300, public" \
"Portal CSS (wvz-design) should be cached 5 min"
assert_cache_header \
"$BASE/wvz-bo/ui/theme/webbloqs-4-0-4/css/webbloqs.css" \
"max-age=300, public" \
"Backoffice theme CSS should be cached 5 min"
echo "=== Results: $PASS passed, $FAIL failed ==="
[[ $FAIL -eq 0 ]]$ curl -s -D - -o /dev/null http://localhost:8080/wvz-rs/ui/theme/webbloqs-4-0-4/css/webbloqs.css
HTTP/1.1 200
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
$ curl -s -D - -o /dev/null http://localhost:8080/wvz-rs/ui/wvz-design.css
HTTP/1.1 200
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
$ curl -s -D - -o /dev/null http://localhost:8080/wvz-bo/ui/theme/webbloqs-4-0-4/css/webbloqs.css
HTTP/1.1 200
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Test script output:
=== Cache-Control Header Tests ===
Base URL: http://localhost:8080
FAIL: Theme CSS (webbloqs) should be cached 5 min
http://localhost:8080/wvz-rs/ui/theme/webbloqs-4-0-4/css/webbloqs.css
HTTP/1.1 200
expected: Cache-Control: max-age=300, public
actual: Cache-Control: no-cache, no-store, max-age=0, must-revalidate
FAIL: Portal CSS (wvz-design) should be cached 5 min
http://localhost:8080/wvz-rs/ui/wvz-design.css
HTTP/1.1 200
expected: Cache-Control: max-age=300, public
actual: Cache-Control: no-cache, no-store, max-age=0, must-revalidate
FAIL: Backoffice theme CSS should be cached 5 min
http://localhost:8080/wvz-bo/ui/theme/webbloqs-4-0-4/css/webbloqs.css
HTTP/1.1 200
expected: Cache-Control: max-age=300, public
actual: Cache-Control: no-cache, no-store, max-age=0, must-revalidate
=== Results: 0 passed, 3 failed ===
$ curl -s -D - -o /dev/null http://localhost:8080/wvz-rs/ui/theme/webbloqs-4-0-4/css/webbloqs.css
HTTP/1.1 200
Cache-Control: max-age=300, public
$ curl -s -D - -o /dev/null http://localhost:8080/wvz-rs/ui/wvz-design.css
HTTP/1.1 200
Cache-Control: max-age=300, public
$ curl -s -D - -o /dev/null http://localhost:8080/wvz-bo/ui/theme/webbloqs-4-0-4/css/webbloqs.css
HTTP/1.1 200
Cache-Control: max-age=300, public
$ curl -s -D - -o /dev/null http://localhost:8080/health
HTTP/1.1 200
Cache-Control: no-cache, no-store, max-age=0, must-revalidate ← API still no-cache, correct
Test script output:
=== Cache-Control Header Tests ===
Base URL: http://localhost:8080
PASS: Theme CSS (webbloqs) should be cached 5 min
http://localhost:8080/wvz-rs/ui/theme/webbloqs-4-0-4/css/webbloqs.css
HTTP/1.1 200 → Cache-Control: max-age=300, public
PASS: Portal CSS (wvz-design) should be cached 5 min
http://localhost:8080/wvz-rs/ui/wvz-design.css
HTTP/1.1 200 → Cache-Control: max-age=300, public
PASS: Backoffice theme CSS should be cached 5 min
http://localhost:8080/wvz-bo/ui/theme/webbloqs-4-0-4/css/webbloqs.css
HTTP/1.1 200 → Cache-Control: max-age=300, public
=== Results: 3 passed, 0 failed ===
| File | Change |
|---|---|
backend/.../WebResourceConfiguration.java |
Added .setCacheControl(CacheControl.maxAge(5min).cachePublic()) to all 5 handlers |
application.yml |
Removed dead spring.web.resources.cache block |
application-dev.yml |
Removed dead spring.web.resources.cache block |
application-quatico-test.yml |
Removed dead spring.web.resources.cache block |
backend/.../MagellanRestController.java |
Fixed pre-existing compile error: getFirst() → get(0) |