Initial commit: AetherForge Linux (forge-mesh) v0.1.0-dev
Some checks failed
Test / test (push) Has been cancelled
Some checks failed
Test / test (push) Has been cancelled
This commit is contained in:
201
scripts/e2e-test.sh
Executable file
201
scripts/e2e-test.sh
Executable file
@@ -0,0 +1,201 @@
|
||||
#!/usr/bin/env bash
|
||||
# AetherForge Linux end-to-end operator flow tests
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
# shellcheck source=scripts/e2e-lib.sh
|
||||
source "${ROOT}/scripts/e2e-lib.sh"
|
||||
|
||||
PORT="${E2E_PORT:-18989}"
|
||||
STRATUM_XMR_PORT="${E2E_STRATUM_XMR:-33333}"
|
||||
STRATUM_RVN_PORT="${E2E_STRATUM_RVN:-33388}"
|
||||
BASE="http://127.0.0.1:${PORT}"
|
||||
CONFIG="${E2E_CONFIG:-/tmp/forge-mesh-e2e-config.json}"
|
||||
FLEET_SECRET="${E2E_FLEET_SECRET:-e2e-fleet-secret-test}"
|
||||
E2E_USER="${E2E_USER:-admin}"
|
||||
E2E_PASS="${E2E_PASS:-changeme}"
|
||||
SERVER_PID=""
|
||||
AGENT_PID=""
|
||||
MOCK_OLLAMA_PID=""
|
||||
MOCK_TG_PID=""
|
||||
|
||||
cleanup() {
|
||||
[[ -n "${AGENT_PID}" ]] && kill "${AGENT_PID}" 2>/dev/null || true
|
||||
[[ -n "${MOCK_OLLAMA_PID}" ]] && kill "${MOCK_OLLAMA_PID}" 2>/dev/null || true
|
||||
[[ -n "${MOCK_TG_PID}" ]] && kill "${MOCK_TG_PID}" 2>/dev/null || true
|
||||
e2e_stop_server
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
start_mock_ollama() {
|
||||
if curl -fsS http://127.0.0.1:11434/api/tags >/dev/null 2>&1; then
|
||||
e2e_pass "Ollama already available — using live instance"
|
||||
return 0
|
||||
fi
|
||||
python3 - <<'PY' &
|
||||
import json
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
|
||||
class H(BaseHTTPRequestHandler):
|
||||
def log_message(self, *a): pass
|
||||
def do_GET(self):
|
||||
if self.path.startswith("/api/tags"):
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps({"models": [{"name": "mock"}]}).encode())
|
||||
else:
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
|
||||
HTTPServer(("127.0.0.1", 11434), H).serve_forever()
|
||||
PY
|
||||
MOCK_OLLAMA_PID=$!
|
||||
sleep 0.5
|
||||
e2e_pass "mock Ollama listening on :11434"
|
||||
}
|
||||
|
||||
start_mock_telegram() {
|
||||
# Telegram is disabled in e2e config; mock not required unless enabled.
|
||||
e2e_pass "Telegram disabled in test config (mock skipped)"
|
||||
}
|
||||
|
||||
e2e_log "building server + agent"
|
||||
make server agent
|
||||
|
||||
e2e_log "writing L4 test config"
|
||||
e2e_write_config "${CONFIG}" "${PORT}" 4 "${STRATUM_XMR_PORT}" "${STRATUM_RVN_PORT}"
|
||||
|
||||
e2e_log "starting server (start/stop lifecycle)"
|
||||
e2e_start_server "${CONFIG}"
|
||||
e2e_pass "server started pid=${SERVER_PID}"
|
||||
|
||||
e2e_log "health check"
|
||||
HEALTH="$(curl -fsS "${BASE}/api/v1/health")"
|
||||
echo "${HEALTH}" | grep -q '"status":"ok"' || { echo "health failed: ${HEALTH}"; exit 1; }
|
||||
e2e_pass "GET /api/v1/health"
|
||||
|
||||
e2e_log "basic auth rejection/acceptance"
|
||||
CODE="$(curl -s -o /dev/null -w '%{http_code}' "${BASE}/api/v1/fleet")"
|
||||
[[ "${CODE}" == "401" ]] || { echo "expected 401 without auth, got ${CODE}"; exit 1; }
|
||||
e2e_pass "unauthenticated fleet → 401"
|
||||
FLEET="$(e2e_curl_auth "${BASE}/api/v1/fleet")"
|
||||
echo "${FLEET}" | grep -q '"hosts"' || { echo "fleet list failed: ${FLEET}"; exit 1; }
|
||||
e2e_pass "authenticated fleet → 200"
|
||||
|
||||
e2e_log "public install.sh render + bash syntax"
|
||||
INSTALL="$(curl -fsS "${BASE}/install.sh")"
|
||||
echo "${INSTALL}" | grep -q 'forge-mesh' || { echo "install.sh missing marker"; exit 1; }
|
||||
TMP_INSTALL="$(mktemp)"
|
||||
echo "${INSTALL}" > "${TMP_INSTALL}"
|
||||
bash -n "${TMP_INSTALL}"
|
||||
rm -f "${TMP_INSTALL}"
|
||||
e2e_pass "GET /install.sh valid bash"
|
||||
|
||||
e2e_log "forge build + public download"
|
||||
BUILD_RESP="$(e2e_curl_auth -X POST "${BASE}/api/v1/forge/builds/trigger" \
|
||||
-H "Content-Type: application/json" -d '{"public":true}')"
|
||||
echo "${BUILD_RESP}" | grep -q '"ok":true' || { echo "forge trigger failed: ${BUILD_RESP}"; exit 1; }
|
||||
LATEST="$(curl -fsS "${BASE}/api/v1/public/builds/latest?os=linux&arch=amd64")"
|
||||
BUILD_ID="$(e2e_json_field "${LATEST}" id)"
|
||||
[[ -n "${BUILD_ID}" ]] || { echo "no build id in ${LATEST}"; exit 1; }
|
||||
DOWNLOAD_BYTES="$(curl -fsS "${BASE}/api/v1/public/download/${BUILD_ID}" | wc -c | tr -d ' ')"
|
||||
[[ "${DOWNLOAD_BYTES}" -gt 100 ]] || { echo "download too small: ${DOWNLOAD_BYTES}"; exit 1; }
|
||||
e2e_pass "forge build + public download (${BUILD_ID})"
|
||||
|
||||
e2e_log "agent register via REST + WS subprocess"
|
||||
REGISTER="$(e2e_curl_fleet -X POST "${BASE}/api/v1/fleet/register" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"hostname":"e2e-rest-host","fingerprint":"127.0.0.2","arch":"amd64"}')"
|
||||
HOST_ID="$(e2e_json_field "${REGISTER}" host_id)"
|
||||
[[ -n "${HOST_ID}" ]] || { echo "register failed: ${REGISTER}"; exit 1; }
|
||||
e2e_pass "POST /api/v1/fleet/register"
|
||||
|
||||
AGENT_HOST="e2e-ws-agent"
|
||||
FORGE_FLEET_SECRET="${FLEET_SECRET}" FORGE_HOST_ID="${AGENT_HOST}" \
|
||||
./bin/forge-mesh-agent -deck "${BASE}" -secret "${FLEET_SECRET}" -host-id "${AGENT_HOST}" &
|
||||
AGENT_PID=$!
|
||||
sleep 3
|
||||
|
||||
e2e_log "fleet appears in API"
|
||||
FLEET2="$(e2e_curl_auth "${BASE}/api/v1/fleet/hosts")"
|
||||
echo "${FLEET2}" | grep -q "${AGENT_HOST}" || echo "${FLEET2}" | grep -q 'e2e-rest-host' || {
|
||||
echo "fleet missing enrolled hosts: ${FLEET2}"; exit 1
|
||||
}
|
||||
WS_HOST_ID="$(echo "${FLEET2}" | sed -n 's/.*"hostname":"\('"${AGENT_HOST}"'\)".*"id":"\([^"]*\)".*/\1/p')"
|
||||
[[ -z "${WS_HOST_ID}" ]] && WS_HOST_ID="${HOST_ID}"
|
||||
e2e_pass "fleet hosts visible"
|
||||
|
||||
e2e_log "mining profile push"
|
||||
PROFILE_RESP="$(e2e_curl_auth -X POST "${BASE}/api/v1/fleet/${WS_HOST_ID}/mining-profile" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"wallet_address":"e2e-wallet-addr","name":"E2E profile"}')"
|
||||
echo "${PROFILE_RESP}" | grep -q '"ok":true' || { echo "profile push failed: ${PROFILE_RESP}"; exit 1; }
|
||||
e2e_pass "POST /api/v1/fleet/{id}/mining-profile"
|
||||
|
||||
e2e_log "stratum port open"
|
||||
e2e_wait_port 127.0.0.1 "${STRATUM_XMR_PORT}"
|
||||
e2e_wait_port 127.0.0.1 "${STRATUM_RVN_PORT}"
|
||||
# Send minimal stratum-ish payload (accept-only mode)
|
||||
(printf '{"id":1,"method":"mining.subscribe","params":[]}\n' | nc -w 2 127.0.0.1 "${STRATUM_XMR_PORT}") >/dev/null 2>&1 || true
|
||||
e2e_pass "stratum XMR:${STRATUM_XMR_PORT} RVN:${STRATUM_RVN_PORT} listening"
|
||||
|
||||
e2e_log "/spread/ lander serves"
|
||||
SPREAD="$(curl -fsS "${BASE}/spread/?c=e2e-campaign")"
|
||||
echo "${SPREAD}" | grep -qi 'spread\|summon\|install' || { echo "spread lander unexpected: ${SPREAD}"; exit 1; }
|
||||
e2e_pass "GET /spread/"
|
||||
|
||||
e2e_log "policy snapshot"
|
||||
SNAP="$(e2e_curl_auth -X POST "${BASE}/api/v1/policy/snapshot")"
|
||||
SNAP_TOKEN="$(e2e_json_field "${SNAP}" token)"
|
||||
[[ -n "${SNAP_TOKEN}" ]] || { echo "snapshot create failed: ${SNAP}"; exit 1; }
|
||||
SNAP_BODY="$(curl -fsS "${BASE}/api/v1/public/policy-snapshot/${SNAP_TOKEN}")"
|
||||
echo "${SNAP_BODY}" | grep -q 'policy_from_server' || { echo "snapshot fetch failed: ${SNAP_BODY}"; exit 1; }
|
||||
e2e_pass "policy snapshot create + public fetch"
|
||||
|
||||
e2e_log "crucible endpoint (L4)"
|
||||
CRUC="$(e2e_curl_auth -X POST "${BASE}/api/v1/crucible/batch" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"command\":\"status\",\"host_ids\":[\"${WS_HOST_ID}\"]}")"
|
||||
echo "${CRUC}" | grep -q '"status":"completed"' || echo "${CRUC}" | grep -q '"status":"running"' || {
|
||||
echo "crucible failed: ${CRUC}"; exit 1
|
||||
}
|
||||
e2e_pass "POST /api/v1/crucible/batch"
|
||||
|
||||
e2e_log "ws ticket"
|
||||
TICKET="$(e2e_curl_auth -X POST "${BASE}/api/v1/ws/ticket")"
|
||||
echo "${TICKET}" | grep -q 'ticket' || { echo "ws ticket failed: ${TICKET}"; exit 1; }
|
||||
e2e_pass "POST /api/v1/ws/ticket"
|
||||
|
||||
start_mock_ollama
|
||||
start_mock_telegram
|
||||
|
||||
e2e_log "clearance gate blocks L0 actions — restart with operator_clearance=0"
|
||||
kill "${AGENT_PID}" 2>/dev/null || true
|
||||
AGENT_PID=""
|
||||
e2e_stop_server
|
||||
|
||||
L0_CONFIG="/tmp/forge-mesh-e2e-l0.json"
|
||||
e2e_write_config "${L0_CONFIG}" "${PORT}" 0 "${STRATUM_XMR_PORT}" "${STRATUM_RVN_PORT}"
|
||||
e2e_start_server "${L0_CONFIG}"
|
||||
|
||||
ME="$(e2e_curl_auth "${BASE}/api/v1/operator/me")"
|
||||
echo "${ME}" | grep -q '"clearance_level":0' || { echo "operator/me: ${ME}"; exit 1; }
|
||||
|
||||
DENIED_CODE="$(e2e_curl_auth -X POST "${BASE}/api/v1/fleet/${HOST_ID}/command" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"action":"shell","args":{"command":"id"}}' \
|
||||
-o /dev/null -w '%{http_code}')"
|
||||
[[ "${DENIED_CODE}" == "403" ]] || { echo "expected 403 for L0 shell, got ${DENIED_CODE}"; exit 1; }
|
||||
|
||||
CRUC_CODE="$(e2e_curl_auth -X POST "${BASE}/api/v1/crucible/batch" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"command\":\"status\",\"host_ids\":[\"${HOST_ID}\"]}" \
|
||||
-o /dev/null -w '%{http_code}')"
|
||||
[[ "${CRUC_CODE}" == "403" ]] || { echo "expected 403 for L0 crucible, got ${CRUC_CODE}"; exit 1; }
|
||||
e2e_pass "L0 operator blocked from shell + crucible"
|
||||
|
||||
echo ""
|
||||
echo "✓ e2e full operator flow passed ($(grep -c 'e2e_pass\|✓' "$0" || echo 18) checks)"
|
||||
Reference in New Issue
Block a user