#!/usr/bin/env bash # Public-facing E2E checks for Order of Malta DealFlow Command Center (treasury_management_monorepo). # Validates DNS, TLS, health JSON, unauthenticated demo-login, and read-only glossary — same surface as operators hit from the internet (after NPM + DNS). # # Usage: # bash scripts/verify/verify-dealflow-public-e2e.sh # DEALFLOW_SKIP_TLS_VERIFY=1 ... # self-signed / LAN IP only # # Requires: curl, jq, dig (optional; skips DNS test if missing) set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" cd "$PROJECT_ROOT" RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' BASE_URL="${DEALFLOW_PUBLIC_URL:-https://dealflow.d-bis.org}" BASE_URL="${BASE_URL%/}" HOST="${DEALFLOW_PUBLIC_HOST:-}" if [[ -z "$HOST" ]]; then HOST=$(printf '%s' "$BASE_URL" | sed -E 's|^https?://([^/]+).*|\1|') fi CURL_EXTRA=() if [[ "${DEALFLOW_SKIP_TLS_VERIFY:-}" == "1" ]]; then CURL_EXTRA+=( -k ) fi PASS=0 FAIL=0 TOTAL=0 pass() { echo -e "${GREEN}[OK]${NC} $1"; PASS=$((PASS + 1)); TOTAL=$((TOTAL + 1)); } fail() { echo -e "${RED}[FAIL]${NC} $1"; FAIL=$((FAIL + 1)); TOTAL=$((TOTAL + 1)); } skip() { echo -e "${YELLOW}[SKIP]${NC} $1"; TOTAL=$((TOTAL + 1)); } echo "" echo "DealFlow public E2E — $BASE_URL" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" # DNS (optional) if [[ "$HOST" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then pass "DNS skipped (numeric host $HOST)" elif command -v dig >/dev/null 2>&1; then if dig +short "$HOST" @1.1.1.1 2>/dev/null | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'; then ip=$(dig +short "$HOST" @1.1.1.1 2>/dev/null | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' | head -1) pass "DNS $HOST → $ip" else fail "DNS $HOST — no A record (public hostname not published yet?)" fi else skip "DNS (dig not installed)" fi # TLS + health (GET — Nest health routes may not answer HEAD) health_json=$(curl "${CURL_EXTRA[@]}" -sf --connect-timeout 15 --max-time 30 "$BASE_URL/api/health/ready" 2>/dev/null || true) if [[ -z "$health_json" ]]; then fail "GET $BASE_URL/api/health/ready — no response or TLS error" else if printf '%s' "$health_json" | jq -e '.status == "ok"' >/dev/null 2>&1; then pass "Health JSON status ok" else fail "Health JSON missing status ok — $(printf '%.120s' "$health_json")" fi fi # Frontend root (via nginx → Next) root_code=$(curl "${CURL_EXTRA[@]}" -sk -o /dev/null -w '%{http_code}' --connect-timeout 15 "$BASE_URL/" 2>/dev/null || echo "000") if [[ "$root_code" =~ ^2 ]]; then pass "HTTPS GET / → HTTP $root_code" else fail "HTTPS GET / → HTTP $root_code" fi # Demo login (same contract as smoke-test / browser) login_tmp=$(mktemp) trap 'rm -f "$login_tmp"' EXIT login_code=$(curl "${CURL_EXTRA[@]}" -sk -o "$login_tmp" -w '%{http_code}' --connect-timeout 15 \ -X POST "$BASE_URL/api/auth/demo-login" \ -H 'Content-Type: application/json' \ -H "Origin: $BASE_URL" \ -d '{"email":"ceo@dbis.org","role":"CHIEF_EXECUTIVE"}' 2>/dev/null || echo "000") if [[ "$login_code" =~ ^2 ]] && jq -e '.accessToken != null and (.accessToken|length) > 0' "$login_tmp" >/dev/null 2>&1; then pass "POST /api/auth/demo-login → token" else fail "POST /api/auth/demo-login → HTTP $login_code $(head -c 200 "$login_tmp" 2>/dev/null || true)" fi # Public read-only API (no auth) glossary_body=$(curl "${CURL_EXTRA[@]}" -sf --connect-timeout 15 "$BASE_URL/api/glossary" 2>/dev/null || true) if printf '%s' "$glossary_body" | jq -e 'type == "array" and length > 0' >/dev/null 2>&1; then glen=$(printf '%s' "$glossary_body" | jq 'length') pass "GET /api/glossary → $glen terms" else fail "GET /api/glossary — expected non-empty JSON array" fi echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "Results: $PASS passed, $FAIL failed (checks: $TOTAL)" if [[ "$FAIL" -eq 0 ]]; then echo -e "${GREEN}PUBLIC E2E PASSED${NC}" exit 0 fi echo -e "${RED}PUBLIC E2E FAILED${NC}" exit 1