Files
minerops/scripts/test-development-demo.sh
T

174 lines
7.6 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "${repo_root}"
project_name="${MINEROPS_DEMO_TEST_PROJECT:-minerops-demo-test-$$}"
dev_port="${MINEROPS_DEMO_TEST_PORT:-$((21000 + ($$ % 10000)))}"
base_url="http://127.0.0.1:${dev_port}"
tmp_prefix="/tmp/${project_name}"
admin_cookie="${tmp_prefix}-admin.cookies"
compose=(
docker compose
--project-name "${project_name}"
--env-file .env.dev
-f compose.dev.yaml
)
cleanup() {
status=$?
if (( status != 0 )); then
echo
echo "Development demo test failed. Recent service logs:"
"${compose[@]}" logs --no-color --tail 160 || true
fi
rm -f "${tmp_prefix}"-*
"${compose[@]}" down --volumes --remove-orphans >/dev/null 2>&1 || true
exit "${status}"
}
trap cleanup EXIT
status_for() {
local path="$1"
curl --silent --output /dev/null --write-out '%{http_code}' \
-b "${admin_cookie}" -c "${admin_cookie}" "${base_url}${path}"
}
printf '%s\n' 'APP_ENV=production' > "${tmp_prefix}-production.env"
if MINEROPS_DEMO_ENV_FILE="${tmp_prefix}-production.env" \
./scripts/seed-development-demo.sh >"${tmp_prefix}-safety.out" 2>&1; then
echo "Demo seed accepted a production environment." >&2
exit 1
fi
grep -q 'APP_ENV must be exactly development' "${tmp_prefix}-safety.out"
echo "Starting disposable MinerOps data services..."
MINEROPS_DEV_PORT="${dev_port}" "${compose[@]}" up --detach postgres redis
for attempt in $(seq 1 180); do
postgres_ready=false
redis_ready=false
if "${compose[@]}" exec -T postgres sh -ec \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atqc "SELECT 1"' \
2>/dev/null | grep -qx 1; then
postgres_ready=true
fi
if "${compose[@]}" exec -T redis redis-cli ping 2>/dev/null | grep -qx PONG; then
redis_ready=true
fi
if [[ "${postgres_ready}" == "true" && "${redis_ready}" == "true" ]]; then
break
fi
(( attempt < 180 )) || { echo "Disposable data services did not become ready." >&2; exit 1; }
sleep 1
done
echo "Building and starting the disposable backend..."
MINEROPS_DEV_PORT="${dev_port}" "${compose[@]}" up --detach --build --no-deps backend
backend_container="$("${compose[@]}" ps -q backend)"
for attempt in $(seq 1 180); do
backend_health="$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "${backend_container}")"
[[ "${backend_health}" == "healthy" ]] && break
[[ "${backend_health}" != "exited" && "${backend_health}" != "dead" ]] || { echo "Disposable backend exited." >&2; exit 1; }
(( attempt < 180 )) || { echo "Disposable backend did not become ready." >&2; exit 1; }
sleep 1
done
echo "Starting the disposable gateway..."
MINEROPS_DEV_PORT="${dev_port}" "${compose[@]}" up --detach --no-deps gateway
for attempt in $(seq 1 90); do
if curl --fail --silent "${base_url}/api/health" >"${tmp_prefix}-health.json"; then
break
fi
(( attempt < 90 )) || { echo "Disposable gateway did not become ready." >&2; exit 1; }
sleep 1
done
echo "Creating disposable development administrator..."
curl --fail --silent -c "${admin_cookie}" \
-H 'Content-Type: application/json' \
--data '{"username":"admin","display_name":"Test Administrator","email":"admin@example.com","password":"MinerOpsTest!123"}' \
"${base_url}/api/auth/setup-admin" >"${tmp_prefix}-setup.json"
grep -q '"role":"Admin"' "${tmp_prefix}-setup.json"
echo "Seeding and reapplying the deterministic fixture..."
MINEROPS_DEMO_PROJECT="${project_name}" ./scripts/seed-development-demo.sh
MINEROPS_DEMO_PROJECT="${project_name}" ./scripts/seed-development-demo.sh
MINEROPS_DEMO_PROJECT="${project_name}" ./scripts/seed-development-demo.sh --reseed
counts="$(
"${compose[@]}" exec -T postgres sh -ec \
'psql -v ON_ERROR_STOP=1 -At -F "|" -U "$POSTGRES_USER" -d "$POSTGRES_DB"' <<'SQL'
SELECT
count(*) FILTER (WHERE asset_tag LIKE 'DEMO-MIN-%'),
(SELECT count(*) FROM location_positions WHERE source_file = 'MinerOps demo seed'),
(SELECT count(*) FROM location_positions WHERE source_file = 'MinerOps demo seed' AND miner_id IS NULL),
(SELECT count(*) FROM repairs WHERE id::text LIKE 'd4000000-0000-4000-8000-%'),
(SELECT count(*) FROM repairs WHERE id::text LIKE 'd4000000-0000-4000-8000-%' AND status IN ('OPEN', 'IN_PROGRESS', 'WAITING_FOR_PARTS', 'READY_FOR_TEST')),
(SELECT count(*) FROM repair_events WHERE id::text LIKE 'd5000000-0000-4000-8000-%'),
(SELECT count(*) FROM miner_status_snapshots WHERE id::text LIKE 'd3000000-0000-4000-8000-%')
FROM miners;
SQL
)"
[[ "${counts}" == "30|50|23|4|3|8|23" ]]
echo "Checking live authenticated pages and APIs..."
for path in /dashboard /assets/miners /monitoring/map /operations/repairs; do
[[ "$(status_for "${path}")" == "200" ]]
done
curl --fail --silent -b "${admin_cookie}" "${base_url}/monitoring/map" >"${tmp_prefix}-map.html"
curl --fail --silent -b "${admin_cookie}" "${base_url}/assets/minerops-mine-map.js" >"${tmp_prefix}-map.js"
curl --fail --silent -b "${admin_cookie}" "${base_url}/api/location/map" >"${tmp_prefix}-map.json"
curl --fail --silent -b "${admin_cookie}" "${base_url}/api/inventory/miners?limit=100" >"${tmp_prefix}-miners.json"
curl --fail --silent -b "${admin_cookie}" "${base_url}/api/repairs/queue" >"${tmp_prefix}-repairs.json"
for id in mapSite mapRoom mapLayer mapPermissionNote mapViewport mapInspector; do
grep -q "id=\"${id}\"" "${tmp_prefix}-map.html"
done
for layer in temperature hashrate power repair; do
grep -q "\"${layer}\"" "${tmp_prefix}-map.js"
done
! grep -q "Cannot set properties of null" "${tmp_prefix}-map.html"
python3 - "${tmp_prefix}-map.json" "${tmp_prefix}-miners.json" "${tmp_prefix}-repairs.json" <<'PY'
import json
import sys
with open(sys.argv[1], encoding="utf-8") as handle:
mine_map = json.load(handle)
with open(sys.argv[2], encoding="utf-8") as handle:
miners = json.load(handle)
with open(sys.argv[3], encoding="utf-8") as handle:
repairs = json.load(handle)
assert mine_map["meta"]["miner_details_included"] is True
assert mine_map["meta"]["position_count"] == 50
assert len(mine_map["data"]["sites"]) >= 1
assert {room["name"] for room in mine_map["data"]["rooms"]} >= {"Demo Unit A", "Demo Unit B"}
positions = mine_map["data"]["positions"]
assert {position["rack_name"] for position in positions} >= {"A1", "A2", "A3", "B1", "B2"}
assert sum(position["occupied"] for position in positions) == 27
assert sum(not position["occupied"] for position in positions) == 23
assert any(position["active_repair"] for position in positions)
assert any(position["occupied"] and position["observed_at"] is None for position in positions)
assert any(position["hashrate_ths"] == 0 for position in positions)
assert len({position["model"] for position in positions if position["model"]}) == 6
assert len({position["chip_temp_max_c"] for position in positions if position["chip_temp_max_c"] is not None}) >= 5
assert len(miners["data"]) == 30
assert any(miner["asset_id"] == "DEMO-MIN-001" for miner in miners["data"])
assert repairs["data"]["summary"]["total"] == 3
assert {item["latest_event_status"] for item in repairs["data"]["items"]} == {"open", "waiting_parts", "testing"}
PY
for query in \
'query=DEMO-MIN-001' \
'query=DEMO-SERIAL-001' \
'query=02%3A00%3A00%3A00%3A00%3A01' \
'query=M60S'; do
response="$(curl --fail --silent -b "${admin_cookie}" "${base_url}/api/inventory/miners?${query}&limit=100")"
[[ "${response}" == *'"returned":'* ]]
[[ "${response}" != *'"returned":0'* ]]
done
echo "Development demo seed and live UI/API smoke checks passed."