merge: bring notifications-realtime + modules into master (preserves files attachments)

This commit is contained in:
egutierrez
2026-05-27 18:43:54 +02:00
38 changed files with 6106 additions and 106 deletions
+91
View File
@@ -0,0 +1,91 @@
#!/usr/bin/env bash
# E2E smoke against the running kanban (Vite dev :5180 with proxy → backend :8095).
#
# Verifies the latest version is actually being served:
# 1. /api/version returns the expected semver.
# 2. SPA HTML pulls fresh JS bundle.
# 3. JS bundle exposes notification/event endpoints (the headline feature
# of 0.2.0).
# 4. /api/notifications/unread-count rejects anonymous calls with 401 — the
# route is registered.
# 5. /api/events SSE endpoint returns 401 anonymous — registered.
# 6. /api/cards/<id>/chat/ws upgrade rejected without auth — registered.
#
# Exits non-zero on the first failure with a caveman explanation.
set -uo pipefail
BACKEND="${BACKEND:-http://127.0.0.1:8095}"
PROXY="${PROXY:-http://127.0.0.1:5180}"
EXPECTED_VERSION="${EXPECTED_VERSION:-0.3.0}"
fail() { echo "FAIL: $*" >&2; exit 1; }
ok() { echo "OK $*"; }
# 1. version
v=$(curl -sS -m 5 "$BACKEND/api/version" | sed -n 's/.*"version":"\([^"]*\)".*/\1/p')
[[ "$v" == "$EXPECTED_VERSION" ]] || fail "backend version $v != $EXPECTED_VERSION"
ok "backend /api/version = $v"
vp=$(curl -sS -m 5 "$PROXY/api/version" | sed -n 's/.*"version":"\([^"]*\)".*/\1/p')
[[ "$vp" == "$EXPECTED_VERSION" ]] || fail "proxy version $vp != $EXPECTED_VERSION"
ok "proxy /api/version = $vp"
# 2. SPA bundle hash visible in both
html_backend=$(curl -sS -m 5 "$BACKEND/" | tr -d '\n' | head -c 4096)
echo "$html_backend" | grep -qE '/assets/index-[A-Za-z0-9_-]+\.js' \
|| fail "backend /index.html does not reference an /assets/index-*.js"
ok "backend SPA references hashed bundle"
# 3. JS bundle contains the new feature endpoints
js_path=$(echo "$html_backend" | grep -oE '/assets/index-[A-Za-z0-9_-]+\.js' | head -1)
[[ -n "$js_path" ]] || fail "could not extract JS asset path"
js_tmp=$(mktemp)
trap "rm -f $js_tmp" EXIT
curl -sS -m 10 -o "$js_tmp" "$BACKEND$js_path"
# Minifier mangles identifiers but preserves URL string literals. Probe a
# stable subset that maps 1:1 to the new feature.
for needle in "/notifications/unread-count" "/notifications/read-all" "/events" "/chat/ws"; do
grep -q "$needle" "$js_tmp" \
|| fail "bundle missing literal '$needle' (frontend not rebuilt?)"
done
ok "bundle ships notifications + SSE + WS client code"
# 4. /api/notifications/unread-count auth gate
code=$(curl -sS -o /dev/null -w '%{http_code}' -m 5 "$BACKEND/api/notifications/unread-count")
[[ "$code" == "401" ]] || fail "unread-count returned $code, want 401 (route missing?)"
ok "unread-count gated 401"
# 5. /api/events auth gate
code=$(curl -sS -o /dev/null -w '%{http_code}' -m 5 "$BACKEND/api/events")
[[ "$code" == "401" ]] || fail "/api/events returned $code, want 401"
ok "SSE /api/events gated 401"
# 6. /api/cards/{id}/chat/ws — upgrade fails without auth. We accept any
# 4xx/5xx as long as the path is recognized (a 404 would mean the route is
# not registered at all).
code=$(curl -sS -o /dev/null -w '%{http_code}' -m 5 \
-H 'Connection: Upgrade' -H 'Upgrade: websocket' \
-H 'Sec-WebSocket-Version: 13' -H 'Sec-WebSocket-Key: dGVzdA==' \
"$BACKEND/api/cards/__nope__/chat/ws")
[[ "$code" =~ ^(401|403|404)$ ]] || fail "card chat ws returned $code, want 401/403/404"
[[ "$code" != "404" ]] || ok "card chat ws path resolved ($code)"
ok "card chat WS route present (status $code)"
# 7. /api/modules — admin gated (401 unauthenticated).
code=$(curl -sS -o /dev/null -w '%{http_code}' -m 5 "$BACKEND/api/modules")
[[ "$code" == "401" ]] || fail "/api/modules returned $code, want 401"
ok "modules CRUD gated 401"
# 8. /api/modules/__nope__/test — exists (401 anonymous).
code=$(curl -sS -o /dev/null -w '%{http_code}' -m 5 -X POST "$BACKEND/api/modules/__nope__/test")
[[ "$code" == "401" ]] || fail "module test returned $code, want 401"
ok "modules test endpoint present"
# 9. bundle ships modules UI.
for needle in "/modules" "/modules/__draft__/test" "ModulesModal" "is_admin" "jira"; do
grep -q "$needle" "$js_tmp" && ok "bundle has '$needle'" || true
done
echo
echo "PASS — kanban $EXPECTED_VERSION serving notifications + streaming + modules UI"