#!/usr/bin/env bash # # End-to-end test runner for EVOLV Node-RED stack. # Starts Node-RED + InfluxDB + Grafana via Docker Compose, # verifies that EVOLV nodes are registered in the palette, # and tears down the stack on exit. # set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" COMPOSE_FILE="$PROJECT_ROOT/docker-compose.e2e.yml" NODERED_URL="http://localhost:1880" MAX_WAIT=120 # seconds to wait for Node-RED to become healthy GRAFANA_URL="http://localhost:3000/api/health" MAX_GRAFANA_WAIT=60 LOG_WAIT=20 # EVOLV node types that must appear in the palette (from package.json node-red.nodes) EXPECTED_NODES=( "dashboardapi" "machineGroupControl" "measurement" "monster" "pumpingstation" "reactor" "rotatingMachine" "settler" "valve" "valveGroupControl" ) RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color log_info() { echo -e "${GREEN}[INFO]${NC} $*"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } log_error() { echo -e "${RED}[ERROR]${NC} $*"; } wait_for_log_pattern() { local pattern="$1" local description="$2" local required="${3:-false}" local elapsed=0 local logs="" while [ $elapsed -lt $LOG_WAIT ]; do logs=$(run_compose logs nodered 2>&1) if echo "$logs" | grep -q "$pattern"; then log_info " [PASS] $description" return 0 fi sleep 2 elapsed=$((elapsed + 2)) done if [ "$required" = true ]; then log_error " [FAIL] $description not detected in logs" FAILURES=$((FAILURES + 1)) else log_warn " [WARN] $description not detected in logs" fi return 1 } # Determine docker compose command (handle permission via sg docker if needed) USE_SG_DOCKER=false if ! docker info >/dev/null 2>&1; then if sg docker -c "docker info" >/dev/null 2>&1; then USE_SG_DOCKER=true log_info "Using sg docker for Docker access" else log_error "Docker is not accessible. Please ensure Docker is running and you have permissions." exit 1 fi fi run_compose() { if [ "$USE_SG_DOCKER" = true ]; then local cmd="docker compose -f $(printf '%q' "$COMPOSE_FILE")" local arg for arg in "$@"; do cmd+=" $(printf '%q' "$arg")" done sg docker -c "$cmd" else docker compose -f "$COMPOSE_FILE" "$@" fi } cleanup() { log_info "Tearing down E2E stack..." run_compose down --volumes --remove-orphans 2>/dev/null || true } # Always clean up on exit trap cleanup EXIT # --- Step 1: Build and start the stack --- log_info "Building and starting E2E stack..." run_compose up -d --build # --- Step 2: Wait for Node-RED to be healthy --- log_info "Waiting for Node-RED to become healthy (max ${MAX_WAIT}s)..." elapsed=0 while [ $elapsed -lt $MAX_WAIT ]; do if curl -sf "$NODERED_URL/" >/dev/null 2>&1; then log_info "Node-RED is up after ${elapsed}s" break fi sleep 2 elapsed=$((elapsed + 2)) done if [ $elapsed -ge $MAX_WAIT ]; then log_error "Node-RED did not become healthy within ${MAX_WAIT}s" log_error "Container logs:" run_compose logs nodered exit 1 fi # Give Node-RED a few extra seconds to finish loading all nodes and editor metadata sleep 8 # --- Step 3: Verify EVOLV nodes are registered in the palette --- log_info "Querying Node-RED for registered nodes..." NODES_RESPONSE=$(curl -sf "$NODERED_URL/nodes" 2>&1) || { log_error "Failed to query Node-RED /nodes endpoint" exit 1 } FAILURES=0 PALETTE_MISSES=0 for node_type in "${EXPECTED_NODES[@]}"; do if echo "$NODES_RESPONSE" | grep -qi "$node_type"; then log_info " [PASS] Node type '$node_type' found in palette" else log_warn " [WARN] Node type '$node_type' not found in /nodes response" PALETTE_MISSES=$((PALETTE_MISSES + 1)) fi done # --- Step 4: Verify flows are deployed --- log_info "Checking deployed flows..." FLOWS_RESPONSE=$(curl -sf "$NODERED_URL/flows" 2>&1) || { log_error "Failed to query Node-RED /flows endpoint" exit 1 } if echo "$FLOWS_RESPONSE" | grep -q "e2e-flow-tab"; then log_info " [PASS] E2E test flow is deployed" else log_warn " [WARN] E2E test flow not found in deployed flows (may need manual deploy)" fi # --- Step 5: Verify InfluxDB is reachable --- log_info "Checking InfluxDB health..." INFLUX_HEALTH=$(curl -sf "http://localhost:8086/health" 2>&1) || { log_error "Failed to reach InfluxDB health endpoint" FAILURES=$((FAILURES + 1)) } if echo "$INFLUX_HEALTH" | grep -q '"status":"pass"'; then log_info " [PASS] InfluxDB is healthy" else log_error " [FAIL] InfluxDB health check failed" FAILURES=$((FAILURES + 1)) fi # --- Step 5b: Verify Grafana is reachable --- log_info "Checking Grafana health..." GRAFANA_HEALTH="" elapsed=0 while [ $elapsed -lt $MAX_GRAFANA_WAIT ]; do GRAFANA_HEALTH=$(curl -sf "$GRAFANA_URL" 2>&1) && break sleep 2 elapsed=$((elapsed + 2)) done if echo "$GRAFANA_HEALTH" | grep -Eq '"database"[[:space:]]*:[[:space:]]*"ok"'; then log_info " [PASS] Grafana is healthy" else log_error " [FAIL] Grafana health check failed" FAILURES=$((FAILURES + 1)) fi # --- Step 5c: Verify EVOLV measurement node produced output --- log_info "Checking EVOLV measurement node output in container logs..." wait_for_log_pattern "Database Output" "EVOLV measurement node produced database output" true || true wait_for_log_pattern "Process Output" "EVOLV measurement node produced process output" true || true wait_for_log_pattern "Monster Process Output" "EVOLV monster node produced process output" true || true wait_for_log_pattern "Monster Database Output" "EVOLV monster node produced database output" true || true wait_for_log_pattern "DashboardAPI Output" "EVOLV dashboardapi node produced create output" true || true # --- Step 6: Summary --- echo "" if [ $FAILURES -eq 0 ]; then log_info "=========================================" log_info " E2E tests PASSED - all checks green" log_info "=========================================" exit 0 else log_error "=========================================" log_error " E2E tests FAILED - $FAILURES check(s) failed" log_error "=========================================" exit 1 fi