- Updated branding from "SolaceScanScout" to "Solace" across various files including deployment scripts, API responses, and documentation. - Changed default base URL for Playwright tests and updated security headers to reflect the new branding. - Enhanced README and API documentation to include new authentication endpoints and product access details. This refactor aligns the project branding and improves clarity in the API documentation.
419 lines
16 KiB
Bash
Executable File
419 lines
16 KiB
Bash
Executable File
#!/bin/bash
|
||
|
||
# End-to-End Test for explorer.d-bis.org
|
||
# Comprehensive testing of all explorer functionality
|
||
|
||
set -uo pipefail
|
||
|
||
EXPLORER_URL="https://explorer.d-bis.org"
|
||
BASE_URL="http://192.168.11.140"
|
||
TEST_RESULTS=()
|
||
PASSED=0
|
||
FAILED=0
|
||
WARNINGS=0
|
||
|
||
# Colors
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
NC='\033[0m'
|
||
|
||
log_test() {
|
||
local status=$1
|
||
local message=$2
|
||
if [ "$status" = "PASS" ]; then
|
||
echo -e "${GREEN}✅ PASS${NC}: $message"
|
||
((PASSED++))
|
||
TEST_RESULTS+=("PASS: $message")
|
||
elif [ "$status" = "FAIL" ]; then
|
||
echo -e "${RED}❌ FAIL${NC}: $message"
|
||
((FAILED++))
|
||
TEST_RESULTS+=("FAIL: $message")
|
||
elif [ "$status" = "WARN" ]; then
|
||
echo -e "${YELLOW}⚠️ WARN${NC}: $message"
|
||
((WARNINGS++))
|
||
TEST_RESULTS+=("WARN: $message")
|
||
else
|
||
echo -e "${BLUE}ℹ️ INFO${NC}: $message"
|
||
fi
|
||
}
|
||
|
||
test_http_response() {
|
||
local url=$1
|
||
local expected_code=$2
|
||
local description=$3
|
||
|
||
local response_code=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 10 --max-time 30 "$url" 2>/dev/null || echo "000")
|
||
|
||
if [ "$response_code" = "$expected_code" ]; then
|
||
log_test "PASS" "$description (HTTP $response_code)"
|
||
return 0
|
||
elif [ "$response_code" = "000" ]; then
|
||
log_test "FAIL" "$description (Connection failed/timeout)"
|
||
return 1
|
||
else
|
||
log_test "FAIL" "$description (Expected HTTP $expected_code, got $response_code)"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
test_content() {
|
||
local url=$1
|
||
local search_term=$2
|
||
local description=$3
|
||
local extra_opts="${4:-}"
|
||
|
||
local content=$(curl -s -L --max-redirs 3 $extra_opts --connect-timeout 10 --max-time 30 "$url" 2>/dev/null || echo "")
|
||
|
||
if [ -z "$content" ]; then
|
||
log_test "FAIL" "$description (Empty response)"
|
||
return 1
|
||
elif grep -qi "$search_term" <<< "$content"; then
|
||
log_test "PASS" "$description (Found: $search_term)"
|
||
return 0
|
||
else
|
||
# Show first 100 chars for debugging
|
||
PREVIEW=$(printf '%.100s' "$content" | tr -d '\n')
|
||
log_test "FAIL" "$description (Not found: $search_term, got: $PREVIEW...)"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
test_json_response() {
|
||
local url=$1
|
||
local description=$2
|
||
|
||
local response=$(curl -s -L --connect-timeout 10 --max-time 30 "$url" 2>/dev/null || echo "")
|
||
|
||
if echo "$response" | jq . >/dev/null 2>&1; then
|
||
log_test "PASS" "$description (Valid JSON)"
|
||
return 0
|
||
else
|
||
log_test "FAIL" "$description (Invalid JSON or empty response)"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
echo "=========================================="
|
||
echo "End-to-End Test: explorer.d-bis.org"
|
||
echo "=========================================="
|
||
echo "Test URL: $EXPLORER_URL"
|
||
echo "Base URL: $BASE_URL"
|
||
echo "Date: $(date)"
|
||
echo "=========================================="
|
||
echo ""
|
||
|
||
# ============================================
|
||
# 1. Basic Connectivity Tests
|
||
# ============================================
|
||
echo "=== 1. Basic Connectivity Tests ==="
|
||
|
||
# Test HTTPS accessibility (may fail if Cloudflare tunnel not running)
|
||
HTTPS_CODE=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 5 --max-time 10 "$EXPLORER_URL" 2>/dev/null || echo "000")
|
||
if [ "$HTTPS_CODE" = "200" ] || [ "$HTTPS_CODE" = "301" ] || [ "$HTTPS_CODE" = "302" ]; then
|
||
log_test "PASS" "HTTPS homepage accessibility (HTTP $HTTPS_CODE)"
|
||
elif [ "$HTTPS_CODE" = "000" ]; then
|
||
log_test "WARN" "HTTPS homepage not accessible externally (Cloudflare tunnel may be down - testing internal access instead)"
|
||
else
|
||
log_test "WARN" "HTTPS homepage returned HTTP $HTTPS_CODE"
|
||
fi
|
||
|
||
# Test HTTP redirect (301 or 302)
|
||
REDIRECT_CODE=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 10 "http://explorer.d-bis.org" 2>/dev/null || echo "000")
|
||
if [ "$REDIRECT_CODE" = "301" ] || [ "$REDIRECT_CODE" = "302" ]; then
|
||
log_test "PASS" "HTTP to HTTPS redirect (HTTP $REDIRECT_CODE)"
|
||
else
|
||
log_test "FAIL" "HTTP to HTTPS redirect (Expected 301 or 302, got $REDIRECT_CODE)"
|
||
fi
|
||
|
||
# Test direct IP access (internal)
|
||
test_http_response "$BASE_URL:80/" "200" "Direct IP access (port 80)"
|
||
|
||
echo ""
|
||
|
||
# ============================================
|
||
# 2. Frontend Content Tests
|
||
# ============================================
|
||
echo "=== 2. Frontend Content Tests ==="
|
||
|
||
# Use internal URL for content tests since HTTPS may not be accessible
|
||
CONTENT_URL="$BASE_URL:80"
|
||
|
||
# Test homepage content
|
||
test_content "$CONTENT_URL" "SolaceScan" "Homepage contains SolaceScan title"
|
||
|
||
# Test explorer branding
|
||
test_content "$CONTENT_URL" "Explorer" "Homepage contains explorer branding"
|
||
|
||
# Test HTML structure
|
||
test_content "$CONTENT_URL" "<!DOCTYPE html" "Valid HTML document structure"
|
||
|
||
# Test JavaScript loading
|
||
test_content "$CONTENT_URL" "ethers" "JavaScript libraries present"
|
||
|
||
echo ""
|
||
|
||
# ============================================
|
||
# 3. API Endpoint Tests
|
||
# ============================================
|
||
echo "=== 3. API Endpoint Tests ==="
|
||
|
||
# Test Blockscout API stats (via internal nginx)
|
||
test_json_response "$BASE_URL:80/api/v2/stats" "Blockscout API /api/v2/stats endpoint (via nginx)"
|
||
|
||
# Test Blockscout API blocks (via internal nginx)
|
||
test_json_response "$BASE_URL:80/api/v2/blocks" "Blockscout API /api/v2/blocks endpoint (via nginx)"
|
||
|
||
# Test Blockscout API transactions (via internal nginx)
|
||
test_json_response "$BASE_URL:80/api/v2/transactions" "Blockscout API /api/v2/transactions endpoint (via nginx)"
|
||
|
||
# Test direct API access (internal)
|
||
test_json_response "$BASE_URL:4000/api/v2/stats" "Direct Blockscout API access (port 4000)"
|
||
|
||
echo ""
|
||
|
||
# ============================================
|
||
# 4. Security & Headers Tests
|
||
# ============================================
|
||
echo "=== 4. Security & Headers Tests ==="
|
||
|
||
# Test headers from internal nginx
|
||
HEADERS=$(curl -s -I -L --connect-timeout 10 --max-time 30 "$BASE_URL:80/" 2>/dev/null || echo "")
|
||
|
||
if echo "$HEADERS" | grep -qi "strict-transport-security\|HSTS"; then
|
||
log_test "PASS" "HSTS header present"
|
||
else
|
||
log_test "WARN" "HSTS header not found"
|
||
fi
|
||
|
||
if echo "$HEADERS" | grep -qi "x-frame-options"; then
|
||
log_test "PASS" "X-Frame-Options header present"
|
||
else
|
||
log_test "WARN" "X-Frame-Options header not found"
|
||
fi
|
||
|
||
if echo "$HEADERS" | grep -qi "x-content-type-options"; then
|
||
log_test "PASS" "X-Content-Type-Options header present"
|
||
else
|
||
log_test "WARN" "X-Content-Type-Options header not found"
|
||
fi
|
||
|
||
echo ""
|
||
|
||
# ============================================
|
||
# 5. Performance Tests
|
||
# ============================================
|
||
echo "=== 5. Performance Tests ==="
|
||
|
||
# Test performance on internal URL
|
||
START_TIME=$(date +%s%N)
|
||
curl -s -o /dev/null -w '%{time_total}' --connect-timeout 10 --max-time 30 "$BASE_URL:80/" >/dev/null 2>&1
|
||
END_TIME=$(date +%s%N)
|
||
RESPONSE_TIME=$(echo "scale=3; ($END_TIME - $START_TIME) / 1000000000" | bc 2>/dev/null || echo "N/A")
|
||
|
||
if [ "$RESPONSE_TIME" != "N/A" ]; then
|
||
if (( $(echo "$RESPONSE_TIME < 3.0" | bc -l 2>/dev/null || echo 0) )); then
|
||
log_test "PASS" "Response time acceptable (${RESPONSE_TIME}s)"
|
||
else
|
||
log_test "WARN" "Response time slow (${RESPONSE_TIME}s)"
|
||
fi
|
||
else
|
||
log_test "WARN" "Could not measure response time"
|
||
fi
|
||
|
||
echo ""
|
||
|
||
# ============================================
|
||
# 6. Service Status Tests
|
||
# ============================================
|
||
echo "=== 6. Service Status Tests ==="
|
||
|
||
# Test nginx on VMID 5000
|
||
if ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no root@192.168.11.10 "ssh -o ConnectTimeout=5 root@r630-02 'pct exec 5000 -- systemctl is-active nginx 2>/dev/null'" 2>/dev/null | grep -q "active"; then
|
||
log_test "PASS" "Nginx service running on VMID 5000"
|
||
else
|
||
log_test "FAIL" "Nginx service not running on VMID 5000"
|
||
fi
|
||
|
||
# Test Blockscout service
|
||
if ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no root@192.168.11.10 "ssh -o ConnectTimeout=5 root@r630-02 'pct exec 5000 -- systemctl is-active blockscout 2>/dev/null || pct exec 5000 -- docker ps | grep -q blockscout'" 2>/dev/null | grep -qE "active|blockscout"; then
|
||
log_test "PASS" "Blockscout service running on VMID 5000"
|
||
else
|
||
log_test "WARN" "Blockscout service status unknown"
|
||
fi
|
||
|
||
# Test port 80 listening
|
||
if ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no root@192.168.11.10 "ssh -o ConnectTimeout=5 root@r630-02 'pct exec 5000 -- ss -tlnp | grep -q :80'" 2>/dev/null; then
|
||
log_test "PASS" "Port 80 listening on VMID 5000"
|
||
else
|
||
log_test "FAIL" "Port 80 not listening on VMID 5000"
|
||
fi
|
||
|
||
# Test port 4000 listening
|
||
if ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no root@192.168.11.10 "ssh -o ConnectTimeout=5 root@r630-02 'pct exec 5000 -- ss -tlnp | grep -q :4000'" 2>/dev/null; then
|
||
log_test "PASS" "Port 4000 listening on VMID 5000"
|
||
else
|
||
log_test "WARN" "Port 4000 not listening on VMID 5000"
|
||
fi
|
||
|
||
echo ""
|
||
|
||
# ============================================
|
||
# 7. Frontend Functionality Tests
|
||
# ============================================
|
||
echo "=== 7. Frontend Functionality Tests ==="
|
||
|
||
# Check if frontend file exists
|
||
if ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no root@192.168.11.10 "ssh -o ConnectTimeout=5 root@r630-02 'pct exec 5000 -- test -f /var/www/html/index.html'" 2>/dev/null; then
|
||
log_test "PASS" "Frontend HTML file exists"
|
||
else
|
||
log_test "FAIL" "Frontend HTML file not found"
|
||
fi
|
||
|
||
# Check frontend file size (should be substantial)
|
||
FRONTEND_SIZE=$(ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no root@192.168.11.10 "ssh -o ConnectTimeout=5 root@r630-02 'pct exec 5000 -- stat -c%s /var/www/html/index.html 2>/dev/null'" 2>/dev/null || echo "0")
|
||
if [ "$FRONTEND_SIZE" -gt 10000 ]; then
|
||
log_test "PASS" "Frontend file size reasonable (${FRONTEND_SIZE} bytes)"
|
||
else
|
||
log_test "WARN" "Frontend file size suspiciously small (${FRONTEND_SIZE} bytes)"
|
||
fi
|
||
|
||
echo ""
|
||
|
||
# ============================================
|
||
# 8. Network Routing Tests
|
||
# ============================================
|
||
echo "=== 8. Network Routing Tests ==="
|
||
|
||
# Test NPMplus routing (may fail if Cloudflare tunnel down)
|
||
NPMPLUS_CHECK=$(curl -s -I --connect-timeout 5 --max-time 10 "https://explorer.d-bis.org" 2>/dev/null | head -1 || echo "")
|
||
if echo "$NPMPLUS_CHECK" | grep -qE "200|301|302"; then
|
||
log_test "PASS" "NPMplus routing working"
|
||
elif echo "$NPMPLUS_CHECK" | grep -qE "000|timeout"; then
|
||
log_test "WARN" "NPMplus routing timeout (Cloudflare tunnel may be down)"
|
||
else
|
||
log_test "WARN" "NPMplus routing returned: $NPMPLUS_CHECK"
|
||
fi
|
||
|
||
# Test DNS resolution
|
||
if host explorer.d-bis.org 2>/dev/null | grep -q "has address"; then
|
||
log_test "PASS" "DNS resolution working"
|
||
else
|
||
log_test "WARN" "DNS resolution check failed (may be local DNS issue)"
|
||
fi
|
||
|
||
echo ""
|
||
|
||
# ============================================
|
||
# 9. API Data Validation
|
||
# ============================================
|
||
echo "=== 9. API Data Validation ==="
|
||
|
||
# Get stats and validate structure (use internal URL)
|
||
STATS_JSON=$(curl -s -L --connect-timeout 10 --max-time 30 "$BASE_URL:80/api/v2/stats" 2>/dev/null || echo "{}")
|
||
|
||
if echo "$STATS_JSON" | jq -e '.total_blocks' >/dev/null 2>&1; then
|
||
TOTAL_BLOCKS=$(echo "$STATS_JSON" | jq -r '.total_blocks // 0')
|
||
if [ "$TOTAL_BLOCKS" -gt 0 ]; then
|
||
log_test "PASS" "API returns valid block count ($TOTAL_BLOCKS blocks)"
|
||
else
|
||
log_test "WARN" "API returns zero blocks (may be indexing)"
|
||
fi
|
||
else
|
||
log_test "FAIL" "API stats structure invalid"
|
||
fi
|
||
|
||
# Check for chain ID (optional; Blockscout may use different field)
|
||
if echo "$STATS_JSON" | jq -e '.chain_id' >/dev/null 2>&1; then
|
||
CHAIN_ID=$(echo "$STATS_JSON" | jq -r '.chain_id // "unknown"')
|
||
log_test "PASS" "API returns chain ID ($CHAIN_ID)"
|
||
elif echo "$STATS_JSON" | jq -e '.total_blocks' >/dev/null 2>&1; then
|
||
log_test "PASS" "API returns stats (chain ID optional)"
|
||
else
|
||
log_test "WARN" "API does not return chain ID"
|
||
fi
|
||
|
||
echo ""
|
||
|
||
# ============================================
|
||
# 10. Path-Based URL & Link Tests (SPA routing)
|
||
# ============================================
|
||
echo "=== 10. Path-Based URL & Link Tests ==="
|
||
|
||
# Use BASE_URL:80 for path tests (HTTP SPA paths served by nginx)
|
||
PATH_TEST_BASE="$BASE_URL:80"
|
||
PATH_CURL_EXTRA=""
|
||
|
||
# SPA serves index.html for all paths - verify path-based routing is present
|
||
test_content "$PATH_TEST_BASE/address/0x99b3511a2d315a497c8112c1fdd8d508d4b1e506" "fromPath" "Path-based routing code present (address URL)" "$PATH_CURL_EXTRA"
|
||
test_content "$PATH_TEST_BASE/address/0x99b3511a2d315a497c8112c1fdd8d508d4b1e506" "SolaceScan" "Address path serves SPA shell" "$PATH_CURL_EXTRA"
|
||
test_content "$PATH_TEST_BASE/blocks" "SolaceScan" "Blocks path serves SPA" "$PATH_CURL_EXTRA"
|
||
test_content "$PATH_TEST_BASE/transactions" "SolaceScan" "Transactions path serves SPA" "$PATH_CURL_EXTRA"
|
||
test_content "$PATH_TEST_BASE/bridge" "SolaceScan" "Bridge path serves SPA" "$PATH_CURL_EXTRA"
|
||
test_content "$PATH_TEST_BASE/weth" "SolaceScan" "WETH path serves SPA" "$PATH_CURL_EXTRA"
|
||
test_content "$PATH_TEST_BASE/watchlist" "SolaceScan" "Watchlist path serves SPA" "$PATH_CURL_EXTRA"
|
||
|
||
# Verify nav links exist in HTML (use same base)
|
||
test_content "$PATH_TEST_BASE/" "#/home" "Home nav link present" "$PATH_CURL_EXTRA"
|
||
test_content "$PATH_TEST_BASE/" "#/blocks" "Blocks nav link present" "$PATH_CURL_EXTRA"
|
||
test_content "$PATH_TEST_BASE/" "#/transactions" "Transactions nav link present" "$PATH_CURL_EXTRA"
|
||
test_content "$PATH_TEST_BASE/" "#/bridge" "Bridge nav link present" "$PATH_CURL_EXTRA"
|
||
test_content "$PATH_TEST_BASE/" "#/weth" "WETH nav link present" "$PATH_CURL_EXTRA"
|
||
test_content "$PATH_TEST_BASE/" "/snap/" "MetaMask Snap nav link present" "$PATH_CURL_EXTRA"
|
||
|
||
# Verify applyHashRoute handles pathname
|
||
test_content "$PATH_TEST_BASE/" "location.pathname" "Path-based route detection present" "$PATH_CURL_EXTRA"
|
||
|
||
echo ""
|
||
|
||
# ============================================
|
||
# 11. Error Handling Tests
|
||
# ============================================
|
||
echo "=== 11. Error Handling Tests ==="
|
||
|
||
# SPA fallback: unknown paths return index.html (200) or redirect (301/302)
|
||
FALLBACK_CODE=$(curl -s -o /dev/null -w '%{http_code}' --max-redirs 2 --connect-timeout 10 "$BASE_URL:80/nonexistent-page" 2>/dev/null || echo "000")
|
||
if [ "$FALLBACK_CODE" = "200" ] || [ "$FALLBACK_CODE" = "301" ] || [ "$FALLBACK_CODE" = "302" ]; then
|
||
log_test "PASS" "SPA fallback for unknown paths (HTTP $FALLBACK_CODE)"
|
||
else
|
||
log_test "WARN" "SPA fallback returned HTTP $FALLBACK_CODE (expected 200 or redirect)"
|
||
fi
|
||
|
||
# Test API error handling (use internal URL)
|
||
API_ERROR=$(curl -s -L --connect-timeout 10 --max-time 30 "$BASE_URL:80/api/v2/invalid-endpoint" 2>/dev/null || echo "")
|
||
API_ERROR_CODE=$(curl -s -o /dev/null -w '%{http_code}' -L --connect-timeout 10 --max-time 30 "$BASE_URL:80/api/v2/invalid-endpoint" 2>/dev/null || echo "000")
|
||
if [ "$API_ERROR_CODE" = "404" ] || [ "$API_ERROR_CODE" = "400" ] || echo "$API_ERROR" | grep -qiE "error|404|not found"; then
|
||
log_test "PASS" "API error handling works (HTTP $API_ERROR_CODE)"
|
||
else
|
||
log_test "WARN" "API error handling unclear (HTTP $API_ERROR_CODE)"
|
||
fi
|
||
|
||
echo ""
|
||
|
||
# ============================================
|
||
# Summary
|
||
# ============================================
|
||
echo "=========================================="
|
||
echo "Test Summary"
|
||
echo "=========================================="
|
||
echo -e "${GREEN}Passed: $PASSED${NC}"
|
||
echo -e "${RED}Failed: $FAILED${NC}"
|
||
echo -e "${YELLOW}Warnings: $WARNINGS${NC}"
|
||
echo ""
|
||
|
||
TOTAL=$((PASSED + FAILED + WARNINGS))
|
||
if [ $TOTAL -gt 0 ]; then
|
||
PASS_PERCENT=$(echo "scale=1; $PASSED * 100 / $TOTAL" | bc 2>/dev/null || echo "0")
|
||
echo "Pass Rate: ${PASS_PERCENT}%"
|
||
fi
|
||
|
||
echo ""
|
||
if [ $FAILED -eq 0 ]; then
|
||
echo -e "${GREEN}✅ All critical tests passed!${NC}"
|
||
exit 0
|
||
else
|
||
echo -e "${RED}❌ Some tests failed. Review results above.${NC}"
|
||
exit 1
|
||
fi
|